[Intl] Optimize Intl.PluralRules

Previously, Intl.PluralRules was mostly implemented in JavaScript. This
patch moves most of the constructor and parts of other methods to C++.

The size of the Intl.PluralRules object is reduced by not storing
MinimumIntegerDigits, MinimumFractionDigits, MaximumFractionDigits,
MinimumSignificantDigits, MaximumSignificantDigits. Instead these are
looked up from icu::DecimalFormat as required.

Another optimziation is that we don't create the result of
resolvedOptions when the Intl.PluralRules object is constructed, but
instead defer until this method is called. In the future, we may want
to cache the result.

This patch also cleans up several error handling paths that shouldn't
happen with ICU and instead just crashes should it ever happen.

Bug: v8:5751
Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
Change-Id: I84c5aa6c25c35fe2d336693dee1b36bf3dcd4a79
Reviewed-on: https://chromium-review.googlesource.com/1158701
Commit-Queue: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: Jungshik Shin <jshin@chromium.org>
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#54917}
This commit is contained in:
Sathya Gunasekaran 2018-08-06 10:31:26 +01:00 committed by Commit Bot
parent 13ed3e38d1
commit cdb4d913f6
21 changed files with 791 additions and 381 deletions

View File

@ -2168,6 +2168,9 @@ v8_source_set("v8_base") {
"src/objects/js-locale-inl.h",
"src/objects/js-locale.cc",
"src/objects/js-locale.h",
"src/objects/js-plural-rules-inl.h",
"src/objects/js-plural-rules.cc",
"src/objects/js-plural-rules.h",
"src/objects/js-promise-inl.h",
"src/objects/js-promise.h",
"src/objects/js-proxy-inl.h",
@ -2865,6 +2868,9 @@ v8_source_set("v8_base") {
"src/objects/js-locale-inl.h",
"src/objects/js-locale.cc",
"src/objects/js-locale.h",
"src/objects/js-plural-rules-inl.h",
"src/objects/js-plural-rules.cc",
"src/objects/js-plural-rules.h",
"src/objects/js-relative-time-format-inl.h",
"src/objects/js-relative-time-format.cc",
"src/objects/js-relative-time-format.h",

View File

@ -31,6 +31,7 @@
#include "src/objects/js-regexp-string-iterator.h"
#include "src/objects/js-regexp.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-plural-rules.h"
#include "src/objects/js-relative-time-format.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/templates.h"
@ -2968,10 +2969,13 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
{
Handle<JSFunction> plural_rules_constructor = InstallFunction(
isolate_, intl, "PluralRules", JS_OBJECT_TYPE, PluralRules::kSize, 0,
factory->the_hole_value(), Builtins::kIllegal);
native_context()->set_intl_plural_rules_function(
*plural_rules_constructor);
isolate_, intl, "PluralRules", JS_INTL_PLURAL_RULES_TYPE,
JSPluralRules::kSize, 0, factory->the_hole_value(),
Builtins::kPluralRulesConstructor);
plural_rules_constructor->shared()->DontAdaptArguments();
InstallWithIntrinsicDefaultProto(
isolate_, plural_rules_constructor,
Context::INTL_PLURAL_RULES_FUNCTION_INDEX);
Handle<JSObject> prototype(
JSObject::cast(plural_rules_constructor->prototype()), isolate_);

View File

@ -1360,6 +1360,8 @@ namespace internal {
CPP(NumberFormatInternalFormatNumber) \
/* ecma402 #sec-intl.numberformat.prototype.format */ \
CPP(NumberFormatPrototypeFormatNumber) \
/* ecma402 #sec-intl.pluralrules */ \
CPP(PluralRulesConstructor) \
/* ecma402 #sec-intl.RelativeTimeFormat.constructor */ \
CPP(RelativeTimeFormatConstructor) \
/* ecma402 #sec-intl.RelativeTimeFormat.prototype.resolvedOptions */ \

View File

@ -17,6 +17,7 @@
#include "src/objects/intl-objects.h"
#include "src/objects/js-list-format-inl.h"
#include "src/objects/js-locale-inl.h"
#include "src/objects/js-plural-rules-inl.h"
#include "src/objects/js-relative-time-format-inl.h"
#include "unicode/datefmt.h"
@ -1095,5 +1096,40 @@ BUILTIN(StringPrototypeToLocaleUpperCase) {
args.atOrUndefined(isolate, 1)));
}
BUILTIN(PluralRulesConstructor) {
HandleScope scope(isolate);
// 1. If NewTarget is undefined, throw a TypeError exception.
if (args.new_target()->IsUndefined(isolate)) { // [[Call]]
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kConstructorNotFunction,
isolate->factory()->NewStringFromStaticChars(
"Intl.PluralRules")));
}
// [[Construct]]
Handle<JSFunction> target = args.target();
Handle<JSReceiver> new_target = Handle<JSReceiver>::cast(args.new_target());
Handle<Object> locales = args.atOrUndefined(isolate, 1);
Handle<Object> options = args.atOrUndefined(isolate, 2);
// 2. Let pluralRules be ? OrdinaryCreateFromConstructor(newTarget,
// "%PluralRulesPrototype%", « [[InitializedPluralRules]],
// [[Locale]], [[Type]], [[MinimumIntegerDigits]],
// [[MinimumFractionDigits]], [[MaximumFractionDigits]],
// [[MinimumSignificantDigits]], [[MaximumSignificantDigits]] »).
Handle<JSObject> plural_rules_obj;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, plural_rules_obj,
JSObject::New(target, new_target));
Handle<JSPluralRules> plural_rules =
Handle<JSPluralRules>::cast(plural_rules_obj);
// 3. Return ? InitializePluralRules(pluralRules, locales, options).
RETURN_RESULT_OR_FAILURE(
isolate, JSPluralRules::InitializePluralRules(isolate, plural_rules,
locales, options));
}
} // namespace internal
} // namespace v8

View File

@ -209,6 +209,7 @@ Type::bitset BitsetType::Lub(HeapObjectType const& type) {
#ifdef V8_INTL_SUPPORT
case JS_INTL_LIST_FORMAT_TYPE:
case JS_INTL_LOCALE_TYPE:
case JS_INTL_PLURAL_RULES_TYPE:
case JS_INTL_RELATIVE_TIME_FORMAT_TYPE:
#endif // V8_INTL_SUPPORT
case JS_CONTEXT_EXTENSION_OBJECT_TYPE:

View File

@ -78,6 +78,8 @@ enum ContextLookupFlags {
V(ARRAY_FOR_EACH_ITERATOR_INDEX, JSFunction, array_for_each_iterator) \
V(ARRAY_KEYS_ITERATOR_INDEX, JSFunction, array_keys_iterator) \
V(ARRAY_VALUES_ITERATOR_INDEX, JSFunction, array_values_iterator) \
V(CANONICALIZE_LOCALE_LIST_FUNCTION_INDEX, JSFunction, \
canonicalize_locale_list) \
V(ERROR_FUNCTION_INDEX, JSFunction, error_function) \
V(ERROR_TO_STRING, JSFunction, error_to_string) \
V(EVAL_ERROR_FUNCTION_INDEX, JSFunction, eval_error_function) \

View File

@ -760,6 +760,14 @@ function canonicalizeLocaleList(locales) {
return seen;
}
// TODO(ftang): remove the %InstallToContext once
// initializeLocaleList is available in C++
// https://bugs.chromium.org/p/v8/issues/detail?id=7987
%InstallToContext([
"canonicalize_locale_list", canonicalizeLocaleList
]);
function initializeLocaleList(locales) {
return freezeArray(canonicalizeLocaleList(locales));
}
@ -949,84 +957,10 @@ function compare(collator, x, y) {
AddBoundMethod(GlobalIntlCollator, 'compare', compare, 2, COLLATOR_TYPE, false);
function PluralRulesConstructor() {
if (IS_UNDEFINED(new.target)) {
throw %make_type_error(kConstructorNotFunction, "PluralRules");
}
var locales = arguments[0];
var options = arguments[1];
if (IS_UNDEFINED(options)) {
options = {__proto__: null};
}
var getOption = getGetOption(options, 'pluralrules');
var locale = resolveLocale('pluralrules', locales, options);
var internalOptions = {__proto__: null};
%DefineWEProperty(internalOptions, 'type', getOption(
'type', 'string', ['cardinal', 'ordinal'], 'cardinal'));
SetNumberFormatDigitOptions(internalOptions, options, 0, 3);
var requestedLocale = locale.locale;
var resolved = %object_define_properties({__proto__: null}, {
type: {value: internalOptions.type, writable: true},
locale: {writable: true},
maximumFractionDigits: {writable: true},
minimumFractionDigits: {writable: true},
minimumIntegerDigits: {writable: true},
requestedLocale: {value: requestedLocale, writable: true},
});
if (HAS_OWN_PROPERTY(internalOptions, 'minimumSignificantDigits')) {
%DefineWEProperty(resolved, 'minimumSignificantDigits', UNDEFINED);
}
if (HAS_OWN_PROPERTY(internalOptions, 'maximumSignificantDigits')) {
%DefineWEProperty(resolved, 'maximumSignificantDigits', UNDEFINED);
}
%DefineWEProperty(resolved, 'pluralCategories', []);
var pluralRules = %CreatePluralRules(requestedLocale, internalOptions,
resolved);
%MarkAsInitializedIntlObjectOfType(pluralRules, PLURAL_RULES_TYPE);
pluralRules[resolvedSymbol] = resolved;
return pluralRules;
}
%SetCode(GlobalIntlPluralRules, PluralRulesConstructor);
DEFINE_METHOD(
GlobalIntlPluralRules.prototype,
resolvedOptions() {
if (!%IsInitializedIntlObjectOfType(this, PLURAL_RULES_TYPE)) {
throw %make_type_error(kIncompatibleMethodReceiver,
'Intl.PluralRules.prototype.resolvedOptions',
this);
}
var result = {
locale: this[resolvedSymbol].locale,
type: this[resolvedSymbol].type,
minimumIntegerDigits: this[resolvedSymbol].minimumIntegerDigits,
minimumFractionDigits: this[resolvedSymbol].minimumFractionDigits,
maximumFractionDigits: this[resolvedSymbol].maximumFractionDigits,
};
if (HAS_OWN_PROPERTY(this[resolvedSymbol], 'minimumSignificantDigits')) {
defineWECProperty(result, 'minimumSignificantDigits',
this[resolvedSymbol].minimumSignificantDigits);
}
if (HAS_OWN_PROPERTY(this[resolvedSymbol], 'maximumSignificantDigits')) {
defineWECProperty(result, 'maximumSignificantDigits',
this[resolvedSymbol].maximumSignificantDigits);
}
defineWECProperty(result, 'pluralCategories',
%_Call(ArraySlice, this[resolvedSymbol].pluralCategories));
return result;
return %PluralRulesResolvedOptions(this);
}
);
@ -1040,12 +974,6 @@ DEFINE_METHOD(
DEFINE_METHOD(
GlobalIntlPluralRules.prototype,
select(value) {
if (!%IsInitializedIntlObjectOfType(this, PLURAL_RULES_TYPE)) {
throw %make_type_error(kIncompatibleMethodReceiver,
'Intl.PluralRules.prototype.select',
this);
}
return %PluralRulesSelect(this, TO_NUMBER(value) + 0);
}
);

View File

@ -728,6 +728,7 @@ ReturnType BodyDescriptorApply(InstanceType type, T1 p1, T2 p2, T3 p3, T4 p4) {
#ifdef V8_INTL_SUPPORT
case JS_INTL_LIST_FORMAT_TYPE:
case JS_INTL_LOCALE_TYPE:
case JS_INTL_PLURAL_RULES_TYPE:
case JS_INTL_RELATIVE_TIME_FORMAT_TYPE:
#endif // V8_INTL_SUPPORT
case WASM_GLOBAL_TYPE:

View File

@ -28,6 +28,7 @@
#include "src/objects/js-regexp-inl.h"
#include "src/objects/js-regexp-string-iterator-inl.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-plural-rules-inl.h"
#include "src/objects/js-relative-time-format-inl.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/maybe-object.h"
@ -358,6 +359,9 @@ void HeapObject::HeapObjectVerify(Isolate* isolate) {
case JS_INTL_LOCALE_TYPE:
JSLocale::cast(this)->JSLocaleVerify(isolate);
break;
case JS_INTL_PLURAL_RULES_TYPE:
JSPluralRules::cast(this)->JSPluralRulesVerify(isolate);
break;
case JS_INTL_RELATIVE_TIME_FORMAT_TYPE:
JSRelativeTimeFormat::cast(this)->JSRelativeTimeFormatVerify(isolate);
break;
@ -1865,12 +1869,14 @@ void InterpreterData::InterpreterDataVerify(Isolate* isolate) {
#ifdef V8_INTL_SUPPORT
void JSListFormat::JSListFormatVerify(Isolate* isolate) {
JSObjectVerify(isolate);
VerifyObjectField(isolate, kLocaleOffset);
VerifyObjectField(isolate, kFormatterOffset);
VerifyObjectField(isolate, kFlagsOffset);
}
void JSLocale::JSLocaleVerify(Isolate* isolate) {
JSObjectVerify(isolate);
VerifyObjectField(isolate, kLanguageOffset);
VerifyObjectField(isolate, kScriptOffset);
VerifyObjectField(isolate, kRegionOffset);
@ -1885,7 +1891,17 @@ void JSLocale::JSLocaleVerify(Isolate* isolate) {
VerifyObjectField(isolate, kNumberingSystemOffset);
}
void JSPluralRules::JSPluralRulesVerify(Isolate* isolate) {
CHECK(IsJSPluralRules());
JSObjectVerify(isolate);
VerifyObjectField(isolate, kLocaleOffset);
VerifyObjectField(isolate, kTypeOffset);
VerifyObjectField(isolate, kICUPluralRulesOffset);
VerifyObjectField(isolate, kICUDecimalFormatOffset);
}
void JSRelativeTimeFormat::JSRelativeTimeFormatVerify(Isolate* isolate) {
JSObjectVerify(isolate);
VerifyObjectField(isolate, kLocaleOffset);
VerifyObjectField(isolate, kFormatterOffset);
VerifyObjectField(isolate, kFlagsOffset);

View File

@ -219,6 +219,7 @@ namespace internal {
INSTANCE_TYPE_LIST_BEFORE_INTL(V) \
V(JS_INTL_LIST_FORMAT_TYPE) \
V(JS_INTL_LOCALE_TYPE) \
V(JS_INTL_PLURAL_RULES_TYPE) \
V(JS_INTL_RELATIVE_TIME_FORMAT_TYPE) \
INSTANCE_TYPE_LIST_AFTER_INTL(V)
#else

View File

@ -26,6 +26,7 @@
#include "src/objects/js-regexp-inl.h"
#include "src/objects/js-regexp-string-iterator-inl.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-plural-rules-inl.h"
#include "src/objects/js-relative-time-format-inl.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/literal-objects-inl.h"
@ -310,6 +311,9 @@ void HeapObject::HeapObjectPrint(std::ostream& os) { // NOLINT
case JS_INTL_LOCALE_TYPE:
JSLocale::cast(this)->JSLocalePrint(os);
break;
case JS_INTL_PLURAL_RULES_TYPE:
JSPluralRules::cast(this)->JSPluralRulesPrint(os);
break;
case JS_INTL_RELATIVE_TIME_FORMAT_TYPE:
JSRelativeTimeFormat::cast(this)->JSRelativeTimeFormatPrint(os);
break;
@ -1976,6 +1980,16 @@ void JSLocale::JSLocalePrint(std::ostream& os) { // NOLINT
os << "\n";
}
void JSPluralRules::JSPluralRulesPrint(std::ostream& os) { // NOLINT
HeapObject::PrintHeader(os, "JSPluralRules");
JSObjectPrint(os);
os << "\n - locale: " << Brief(locale());
os << "\n - type: " << Brief(type());
os << "\n - icu plural rules: " << Brief(icu_plural_rules());
os << "\n - icu decimal format: " << Brief(icu_decimal_format());
os << "\n";
}
void JSRelativeTimeFormat::JSRelativeTimeFormatPrint(
std::ostream& os) { // NOLINT
JSObjectPrintHeader(os, this, "JSRelativeTimeFormat");

View File

@ -68,6 +68,7 @@
#include "src/objects/js-regexp-inl.h"
#include "src/objects/js-regexp-string-iterator.h"
#ifdef V8_INTL_SUPPORT
#include "src/objects/js-plural-rules.h"
#include "src/objects/js-relative-time-format.h"
#endif // V8_INTL_SUPPORT
#include "src/objects/literal-objects-inl.h"
@ -1422,6 +1423,8 @@ int JSObject::GetHeaderSize(InstanceType type,
return JSListFormat::kSize;
case JS_INTL_LOCALE_TYPE:
return JSLocale::kSize;
case JS_INTL_PLURAL_RULES_TYPE:
return JSPluralRules::kSize;
case JS_INTL_RELATIVE_TIME_FORMAT_TYPE:
return JSRelativeTimeFormat::kSize;
#endif // V8_INTL_SUPPORT
@ -3170,6 +3173,7 @@ VisitorId Map::GetVisitorId(Map* map) {
#ifdef V8_INTL_SUPPORT
case JS_INTL_LIST_FORMAT_TYPE:
case JS_INTL_LOCALE_TYPE:
case JS_INTL_PLURAL_RULES_TYPE:
case JS_INTL_RELATIVE_TIME_FORMAT_TYPE:
#endif // V8_INTL_SUPPORT
case WASM_GLOBAL_TYPE:
@ -13211,6 +13215,9 @@ bool CanSubclassHaveInobjectProperties(InstanceType instance_type) {
case JS_DATE_TYPE:
case JS_FUNCTION_TYPE:
case JS_GENERATOR_OBJECT_TYPE:
#ifdef V8_INTL_SUPPORT
case JS_INTL_PLURAL_RULES_TYPE:
#endif
case JS_ASYNC_GENERATOR_OBJECT_TYPE:
case JS_MAP_TYPE:
case JS_MESSAGE_OBJECT_TYPE:

View File

@ -77,6 +77,7 @@
// - JSModuleNamespace
// - JSListFormat // If V8_INTL_SUPPORT enabled.
// - JSLocale // If V8_INTL_SUPPORT enabled.
// - JSPluralRules // If V8_INTL_SUPPORT enabled.
// - JSRelativeTimeFormat // If V8_INTL_SUPPORT enabled.
// - WasmGlobalObject
// - WasmInstanceObject
@ -585,6 +586,7 @@ enum InstanceType : uint16_t {
#ifdef V8_INTL_SUPPORT
JS_INTL_LIST_FORMAT_TYPE,
JS_INTL_LOCALE_TYPE,
JS_INTL_PLURAL_RULES_TYPE,
JS_INTL_RELATIVE_TIME_FORMAT_TYPE,
#endif // V8_INTL_SUPPORT
@ -701,6 +703,7 @@ class JSGlobalProxy;
#ifdef V8_INTL_SUPPORT
class JSListFormat;
class JSLocale;
class JSPluralRules;
class JSRelativeTimeFormat;
#endif // V8_INTL_SUPPORT
class JSPromise;
@ -912,6 +915,7 @@ class ZoneForwardList;
HEAP_OBJECT_ORDINARY_TYPE_LIST_BASE(V) \
V(JSListFormat) \
V(JSLocale) \
V(JSPluralRules) \
V(JSRelativeTimeFormat)
#else
#define HEAP_OBJECT_ORDINARY_TYPE_LIST(V) HEAP_OBJECT_ORDINARY_TYPE_LIST_BASE(V)
@ -1028,10 +1032,11 @@ class ZoneForwardList;
#ifdef V8_INTL_SUPPORT
#define INSTANCE_TYPE_CHECKERS_SINGLE(V) \
INSTANCE_TYPE_CHECKERS_SINGLE_BASE(V) \
V(JSListFormat, JS_INTL_LIST_FORMAT_TYPE) \
V(JSLocale, JS_INTL_LOCALE_TYPE) \
#define INSTANCE_TYPE_CHECKERS_SINGLE(V) \
INSTANCE_TYPE_CHECKERS_SINGLE_BASE(V) \
V(JSListFormat, JS_INTL_LIST_FORMAT_TYPE) \
V(JSLocale, JS_INTL_LOCALE_TYPE) \
V(JSPluralRules, JS_INTL_PLURAL_RULES_TYPE) \
V(JSRelativeTimeFormat, JS_INTL_RELATIVE_TIME_FORMAT_TYPE)
#else

View File

@ -94,34 +94,6 @@ bool ExtractBooleanSetting(Isolate* isolate, Handle<JSObject> options,
return false;
}
icu::Locale CreateICULocale(Isolate* isolate, Handle<String> bcp47_locale_str) {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
v8::String::Utf8Value bcp47_locale(v8_isolate,
v8::Utils::ToLocal(bcp47_locale_str));
CHECK_NOT_NULL(*bcp47_locale);
DisallowHeapAllocation no_gc;
// Convert BCP47 into ICU locale format.
UErrorCode status = U_ZERO_ERROR;
char icu_result[ULOC_FULLNAME_CAPACITY];
int icu_length = 0;
// bcp47_locale_str should be a canonicalized language tag, which
// means this shouldn't fail.
uloc_forLanguageTag(*bcp47_locale, icu_result, ULOC_FULLNAME_CAPACITY,
&icu_length, &status);
CHECK(U_SUCCESS(status));
CHECK_LT(0, icu_length);
icu::Locale icu_locale(icu_result);
if (icu_locale.isBogus()) {
FATAL("Failed to create ICU locale, are ICU data files missing?");
}
return icu_locale;
}
icu::SimpleDateFormat* CreateICUDateFormat(Isolate* isolate,
const icu::Locale& icu_locale,
Handle<JSObject> options) {
@ -707,89 +679,6 @@ void SetResolvedCollatorSettings(Isolate* isolate,
}
}
bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
Handle<JSObject> options, icu::PluralRules** pl,
icu::DecimalFormat** nf) {
// Make formatter from options. Numbering system is added
// to the locale as Unicode extension (if it was specified at all).
UErrorCode status = U_ZERO_ERROR;
UPluralType type = UPLURAL_TYPE_CARDINAL;
icu::UnicodeString type_string;
if (ExtractStringSetting(isolate, options, "type", &type_string)) {
if (type_string == UNICODE_STRING_SIMPLE("ordinal")) {
type = UPLURAL_TYPE_ORDINAL;
} else {
CHECK(type_string == UNICODE_STRING_SIMPLE("cardinal"));
}
}
icu::PluralRules* plural_rules =
icu::PluralRules::forLocale(icu_locale, type, status);
if (U_FAILURE(status)) {
delete plural_rules;
return false;
}
icu::DecimalFormat* number_format = static_cast<icu::DecimalFormat*>(
icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status));
if (U_FAILURE(status)) {
delete plural_rules;
delete number_format;
return false;
}
*pl = plural_rules;
*nf = number_format;
SetNumericSettings(isolate, number_format, options);
// Set rounding mode.
return true;
}
bool SetResolvedPluralRulesSettings(Isolate* isolate,
const icu::Locale& icu_locale,
icu::PluralRules* plural_rules,
icu::DecimalFormat* number_format,
Handle<JSObject> resolved) {
SetResolvedNumericSettings(isolate, icu_locale, number_format, resolved);
Factory* factory = isolate->factory();
Handle<JSObject> pluralCategories = Handle<JSObject>::cast(
JSObject::GetProperty(
isolate, resolved,
factory->NewStringFromStaticChars("pluralCategories"))
.ToHandleChecked());
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::StringEnumeration> categories(
plural_rules->getKeywords(status));
if (U_FAILURE(status)) return false;
if (U_FAILURE(status)) return false;
for (int32_t i = 0;; i++) {
const icu::UnicodeString* category = categories->snext(status);
if (U_FAILURE(status)) return false;
if (category == nullptr) return true;
std::string keyword;
Handle<String> value = factory->NewStringFromAsciiChecked(
category->toUTF8String(keyword).data());
LookupIterator it(isolate, pluralCategories, i, LookupIterator::OWN);
JSObject::DefineOwnPropertyIgnoreAttributes(&it, value,
PropertyAttributes::NONE)
.ToHandleChecked();
}
}
icu::BreakIterator* CreateICUBreakIterator(Isolate* isolate,
const icu::Locale& icu_locale,
Handle<JSObject> options) {
@ -848,11 +737,40 @@ void SetResolvedBreakIteratorSettings(Isolate* isolate,
}
} // namespace
icu::Locale Intl::CreateICULocale(Isolate* isolate,
Handle<String> bcp47_locale_str) {
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
v8::String::Utf8Value bcp47_locale(v8_isolate,
v8::Utils::ToLocal(bcp47_locale_str));
CHECK_NOT_NULL(*bcp47_locale);
DisallowHeapAllocation no_gc;
// Convert BCP47 into ICU locale format.
UErrorCode status = U_ZERO_ERROR;
char icu_result[ULOC_FULLNAME_CAPACITY];
int icu_length = 0;
// bcp47_locale_str should be a canonicalized language tag, which
// means this shouldn't fail.
uloc_forLanguageTag(*bcp47_locale, icu_result, ULOC_FULLNAME_CAPACITY,
&icu_length, &status);
CHECK(U_SUCCESS(status));
CHECK_LT(0, icu_length);
icu::Locale icu_locale(icu_result);
if (icu_locale.isBogus()) {
FATAL("Failed to create ICU locale, are ICU data files missing?");
}
return icu_locale;
}
// static
icu::SimpleDateFormat* DateFormat::InitializeDateTimeFormat(
Isolate* isolate, Handle<String> locale, Handle<JSObject> options,
Handle<JSObject> resolved) {
icu::Locale icu_locale = CreateICULocale(isolate, locale);
icu::Locale icu_locale = Intl::CreateICULocale(isolate, locale);
DCHECK(!icu_locale.isBogus());
icu::SimpleDateFormat* date_format =
@ -889,7 +807,7 @@ void DateFormat::DeleteDateFormat(const v8::WeakCallbackInfo<void>& data) {
icu::DecimalFormat* NumberFormat::InitializeNumberFormat(
Isolate* isolate, Handle<String> locale, Handle<JSObject> options,
Handle<JSObject> resolved) {
icu::Locale icu_locale = CreateICULocale(isolate, locale);
icu::Locale icu_locale = Intl::CreateICULocale(isolate, locale);
DCHECK(!icu_locale.isBogus());
icu::DecimalFormat* number_format =
@ -929,7 +847,7 @@ icu::Collator* Collator::InitializeCollator(Isolate* isolate,
Handle<String> locale,
Handle<JSObject> options,
Handle<JSObject> resolved) {
icu::Locale icu_locale = CreateICULocale(isolate, locale);
icu::Locale icu_locale = Intl::CreateICULocale(isolate, locale);
DCHECK(!icu_locale.isBogus());
icu::Collator* collator = CreateICUCollator(isolate, icu_locale, options);
@ -957,56 +875,10 @@ icu::Collator* Collator::UnpackCollator(Handle<JSObject> obj) {
return Managed<icu::Collator>::cast(obj->GetEmbedderField(0))->raw();
}
void PluralRules::InitializePluralRules(Isolate* isolate, Handle<String> locale,
Handle<JSObject> options,
Handle<JSObject> resolved,
icu::PluralRules** plural_rules,
icu::DecimalFormat** number_format) {
icu::Locale icu_locale = CreateICULocale(isolate, locale);
DCHECK(!icu_locale.isBogus());
bool success = CreateICUPluralRules(isolate, icu_locale, options,
plural_rules, number_format);
if (!success) {
// Remove extensions and try again.
icu::Locale no_extension_locale(icu_locale.getBaseName());
success = CreateICUPluralRules(isolate, no_extension_locale, options,
plural_rules, number_format);
if (!success) {
FATAL("Failed to create ICU PluralRules, are ICU data files missing?");
}
// Set resolved settings (pattern, numbering system).
success = SetResolvedPluralRulesSettings(
isolate, no_extension_locale, *plural_rules, *number_format, resolved);
} else {
success = SetResolvedPluralRulesSettings(isolate, icu_locale, *plural_rules,
*number_format, resolved);
}
CHECK_NOT_NULL(*plural_rules);
CHECK_NOT_NULL(*number_format);
}
icu::PluralRules* PluralRules::UnpackPluralRules(Handle<JSObject> obj) {
return reinterpret_cast<icu::PluralRules*>(obj->GetEmbedderField(0));
}
icu::DecimalFormat* PluralRules::UnpackNumberFormat(Handle<JSObject> obj) {
return reinterpret_cast<icu::DecimalFormat*>(obj->GetEmbedderField(1));
}
void PluralRules::DeletePluralRules(const v8::WeakCallbackInfo<void>& data) {
delete reinterpret_cast<icu::PluralRules*>(data.GetInternalField(0));
delete reinterpret_cast<icu::DecimalFormat*>(data.GetInternalField(1));
GlobalHandles::Destroy(reinterpret_cast<Object**>(data.GetParameter()));
}
icu::BreakIterator* V8BreakIterator::InitializeBreakIterator(
Isolate* isolate, Handle<String> locale, Handle<JSObject> options,
Handle<JSObject> resolved) {
icu::Locale icu_locale = CreateICULocale(isolate, locale);
icu::Locale icu_locale = Intl::CreateICULocale(isolate, locale);
DCHECK(!icu_locale.isBogus());
icu::BreakIterator* break_iterator =
@ -1471,8 +1343,7 @@ MaybeHandle<JSObject> Intl::ResolveLocale(Isolate* isolate, const char* service,
Handle<JSFunction> resolve_locale_function = isolate->resolve_locale();
Handle<Object> result;
Handle<Object> undefined_value(ReadOnlyRoots(isolate).undefined_value(),
isolate);
Handle<Object> undefined_value = isolate->factory()->undefined_value();
Handle<Object> args[] = {service_str, requestedLocales, options};
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
@ -1483,6 +1354,23 @@ MaybeHandle<JSObject> Intl::ResolveLocale(Isolate* isolate, const char* service,
return Handle<JSObject>::cast(result);
}
MaybeHandle<JSObject> Intl::CanonicalizeLocaleList(Isolate* isolate,
Handle<Object> locales) {
Handle<JSFunction> canonicalize_locale_list_function =
isolate->canonicalize_locale_list();
Handle<Object> result;
Handle<Object> undefined_value = isolate->factory()->undefined_value();
Handle<Object> args[] = {locales};
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
Execution::Call(isolate, canonicalize_locale_list_function,
undefined_value, arraysize(args), args),
JSObject);
return Handle<JSObject>::cast(result);
}
Maybe<bool> Intl::GetStringOption(Isolate* isolate, Handle<JSReceiver> options,
const char* property,
std::vector<const char*> values,
@ -2045,45 +1933,149 @@ MaybeHandle<String> Intl::NumberToLocaleString(Isolate* isolate,
}
// ecma402/#sec-defaultnumberoption
MaybeHandle<Smi> Intl::DefaultNumberOption(Isolate* isolate,
Handle<Object> value, int min,
int max, int fallback,
Handle<String> property) {
Maybe<int> Intl::DefaultNumberOption(Isolate* isolate, Handle<Object> value,
int min, int max, int fallback,
Handle<String> property) {
// 2. Else, return fallback.
if (value->IsUndefined()) {
return Handle<Smi>(Smi::FromInt(fallback), isolate);
}
if (value->IsUndefined()) return Just(fallback);
// 1. If value is not undefined, then
// a. Let value be ? ToNumber(value).
Handle<Object> value_num;
ASSIGN_RETURN_ON_EXCEPTION(isolate, value_num,
Object::ToNumber(isolate, value), Smi);
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value_num, Object::ToNumber(isolate, value), Nothing<int>());
DCHECK(value_num->IsNumber());
// b. If value is NaN or less than minimum or greater than maximum, throw a
// RangeError exception.
if (value_num->IsNaN() || value_num->Number() < min ||
value_num->Number() > max) {
THROW_NEW_ERROR(
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange, property),
Smi);
Nothing<int>());
}
// The max and min arguments are integers and the above check makes
// sure that we are within the integer range making this double to
// int conversion safe.
//
// c. Return floor(value).
return Handle<Smi>(Smi::FromInt(floor(value_num->Number())), isolate);
return Just(FastD2I(floor(value_num->Number())));
}
// ecma402/#sec-getnumberoption
MaybeHandle<Smi> Intl::GetNumberOption(Isolate* isolate,
Handle<JSReceiver> options,
Handle<String> property, int min,
int max, int fallback) {
Handle<Object> value;
Maybe<int> Intl::GetNumberOption(Isolate* isolate, Handle<JSReceiver> options,
Handle<String> property, int min, int max,
int fallback) {
// 1. Let value be ? Get(options, property).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, value, JSReceiver::GetProperty(isolate, options, property), Smi);
Handle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, JSReceiver::GetProperty(isolate, options, property),
Nothing<int>());
// Return ? DefaultNumberOption(value, minimum, maximum, fallback).
return DefaultNumberOption(isolate, value, min, max, fallback, property);
}
Maybe<int> Intl::GetNumberOption(Isolate* isolate, Handle<JSReceiver> options,
const char* property, int min, int max,
int fallback) {
Handle<String> property_str =
isolate->factory()->NewStringFromAsciiChecked(property);
return GetNumberOption(isolate, options, property_str, min, max, fallback);
}
Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
icu::DecimalFormat* number_format,
Handle<JSReceiver> options,
int mnfd_default,
int mxfd_default) {
CHECK_NOT_NULL(number_format);
// 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>();
}
// 6. Let mnfd be ? GetNumberOption(options, "minimumFractionDigits", 0, 20,
// mnfdDefault).
int mnfd;
if (!GetNumberOption(isolate, options, "minimumFractionDigits", 0, 20,
mnfd_default)
.To(&mnfd)) {
return Nothing<bool>();
}
// 7. Let mxfdActualDefault be max( mnfd, mxfdDefault ).
int mxfd_actual_default = std::max(mnfd, mxfd_default);
// 8. Let mxfd be ? GetNumberOption(options,
// "maximumFractionDigits", mnfd, 20, mxfdActualDefault).
int mxfd;
if (!GetNumberOption(isolate, options, "maximumFractionDigits", mnfd, 20,
mxfd_actual_default)
.To(&mxfd)) {
return Nothing<bool>();
}
// 9. Let mnsd be ? Get(options, "minimumSignificantDigits").
Handle<Object> mnsd_obj;
Handle<String> mnsd_str =
isolate->factory()->NewStringFromStaticChars("minimumSignificantDigits");
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, mnsd_obj, JSReceiver::GetProperty(isolate, options, mnsd_str),
Nothing<bool>());
// 10. Let mxsd be ? Get(options, "maximumSignificantDigits").
Handle<Object> mxsd_obj;
Handle<String> mxsd_str =
isolate->factory()->NewStringFromStaticChars("maximumSignificantDigits");
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, mxsd_obj, JSReceiver::GetProperty(isolate, options, mxsd_str),
Nothing<bool>());
// 11. Set intlObj.[[MinimumIntegerDigits]] to mnid.
number_format->setMinimumIntegerDigits(mnid);
// 12. Set intlObj.[[MinimumFractionDigits]] to mnfd.
number_format->setMinimumFractionDigits(mnfd);
// 13. Set intlObj.[[MaximumFractionDigits]] to mxfd.
number_format->setMaximumFractionDigits(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>();
}
// 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>();
}
significant_digits_used = true;
// 14. c. Set intlObj.[[MinimumSignificantDigits]] to mnsd.
number_format->setMinimumSignificantDigits(mnsd);
// 14. d. Set intlObj.[[MaximumSignificantDigits]] to mxsd.
number_format->setMaximumSignificantDigits(mxsd);
}
number_format->setSignificantDigitsUsed(significant_digits_used);
number_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp);
return Just(true);
}
} // namespace internal
} // namespace v8

View File

@ -15,6 +15,7 @@
#include "src/contexts.h"
#include "src/intl.h"
#include "src/objects.h"
#include "unicode/locid.h"
#include "unicode/uversion.h"
namespace U_ICU_NAMESPACE {
@ -136,41 +137,6 @@ class Collator {
Collator();
};
class PluralRules {
public:
// Create a PluralRules and DecimalFormat for the specificied locale and
// options. Returns false on an ICU failure.
static void InitializePluralRules(Isolate* isolate, Handle<String> locale,
Handle<JSObject> options,
Handle<JSObject> resolved,
icu::PluralRules** plural_rules,
icu::DecimalFormat** decimal_format);
// Unpacks PluralRules object from corresponding JavaScript object.
static icu::PluralRules* UnpackPluralRules(Handle<JSObject> obj);
// Unpacks NumberFormat object from corresponding JavaScript PluralRUles
// object.
static icu::DecimalFormat* UnpackNumberFormat(Handle<JSObject> obj);
// Release memory we allocated for the Collator once the JS object that holds
// the pointer gets garbage collected.
static void DeletePluralRules(const v8::WeakCallbackInfo<void>& data);
// Layout description.
static const int kPluralRules = JSObject::kHeaderSize;
// Values are formatted with this NumberFormat and then parsed as a Number
// to round them based on the options passed into the PluralRules objct.
// TODO(littledan): If a future version of ICU supports the rounding
// built-in to PluralRules, switch to that, see this bug:
// http://bugs.icu-project.org/trac/ticket/12763
static const int kNumberFormat = kPluralRules + kPointerSize;
static const int kSize = kNumberFormat + kPointerSize;
private:
PluralRules();
};
class V8BreakIterator {
public:
// Create a BreakIterator for the specificied locale and options. Returns the
@ -266,6 +232,13 @@ class Intl {
Isolate* isolate, const char* service, Handle<Object> requestedLocales,
Handle<Object> options);
// This currently calls out to the JavaScript implementation of
// CanonicalizeLocaleList.
//
// ecma402/#sec-canonicalizelocalelist
V8_WARN_UNUSED_RESULT static MaybeHandle<JSObject> CanonicalizeLocaleList(
Isolate* isolate, Handle<Object> locales);
// ECMA402 9.2.10. GetOption( options, property, type, values, fallback)
// ecma402/#sec-getoption
//
@ -337,14 +310,25 @@ class Intl {
Handle<Object> options);
// ecma402/#sec-defaultnumberoption
V8_WARN_UNUSED_RESULT static MaybeHandle<Smi> DefaultNumberOption(
V8_WARN_UNUSED_RESULT static Maybe<int> DefaultNumberOption(
Isolate* isolate, Handle<Object> value, int min, int max, int fallback,
Handle<String> property);
// ecma402/#sec-getnumberoption
V8_WARN_UNUSED_RESULT static MaybeHandle<Smi> GetNumberOption(
V8_WARN_UNUSED_RESULT static Maybe<int> GetNumberOption(
Isolate* isolate, Handle<JSReceiver> options, Handle<String> property,
int min, int max, int fallback);
V8_WARN_UNUSED_RESULT static Maybe<int> GetNumberOption(
Isolate* isolate, Handle<JSReceiver> options, const char* property,
int min, int max, int fallback);
// 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);
icu::Locale static CreateICULocale(Isolate* isolate,
Handle<String> bcp47_locale_str);
};
} // namespace internal

View File

@ -0,0 +1,36 @@
// 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
#ifndef V8_OBJECTS_JS_PLURAL_RULES_INL_H_
#define V8_OBJECTS_JS_PLURAL_RULES_INL_H_
#include "src/api-inl.h"
#include "src/objects-inl.h"
#include "src/objects/js-plural-rules.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
ACCESSORS(JSPluralRules, locale, String, kLocaleOffset)
ACCESSORS(JSPluralRules, type, String, kTypeOffset)
ACCESSORS(JSPluralRules, icu_plural_rules, Managed<icu::PluralRules>,
kICUPluralRulesOffset)
ACCESSORS(JSPluralRules, icu_decimal_format, Managed<icu::DecimalFormat>,
kICUDecimalFormatOffset)
CAST_ACCESSOR(JSPluralRules);
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_JS_PLURAL_RULES_INL_H_

View File

@ -0,0 +1,324 @@
// 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-plural-rules.h"
#include "src/isolate-inl.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-plural-rules-inl.h"
#include "unicode/decimfmt.h"
#include "unicode/locid.h"
#include "unicode/numfmt.h"
#include "unicode/plurrule.h"
#include "unicode/strenum.h"
namespace v8 {
namespace internal {
namespace {
bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
const char* type_string,
std::unique_ptr<icu::PluralRules>* pl,
std::unique_ptr<icu::DecimalFormat>* nf) {
// Make formatter from options. Numbering system is added
// to the locale as Unicode extension (if it was specified at all).
UErrorCode status = U_ZERO_ERROR;
UPluralType type = UPLURAL_TYPE_CARDINAL;
if (strcmp(type_string, "ordinal") == 0) {
type = UPLURAL_TYPE_ORDINAL;
} else {
CHECK_EQ(0, strcmp(type_string, "cardinal"));
}
std::unique_ptr<icu::PluralRules> plural_rules(
icu::PluralRules::forLocale(icu_locale, type, status));
if (U_FAILURE(status)) {
return false;
}
CHECK_NOT_NULL(plural_rules.get());
std::unique_ptr<icu::DecimalFormat> number_format(
static_cast<icu::DecimalFormat*>(
icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status)));
if (U_FAILURE(status)) {
return false;
}
CHECK_NOT_NULL(number_format.get());
*pl = std::move(plural_rules);
*nf = std::move(number_format);
return true;
}
void InitializeICUPluralRules(
Isolate* isolate, Handle<String> locale, const char* type,
std::unique_ptr<icu::PluralRules>* plural_rules,
std::unique_ptr<icu::DecimalFormat>* number_format) {
icu::Locale icu_locale = Intl::CreateICULocale(isolate, locale);
DCHECK(!icu_locale.isBogus());
bool success = CreateICUPluralRules(isolate, icu_locale, type, plural_rules,
number_format);
if (!success) {
// Remove extensions and try again.
icu::Locale no_extension_locale(icu_locale.getBaseName());
success = CreateICUPluralRules(isolate, no_extension_locale, type,
plural_rules, number_format);
if (!success) {
FATAL("Failed to create ICU PluralRules, are ICU data files missing?");
}
}
CHECK_NOT_NULL((*plural_rules).get());
CHECK_NOT_NULL((*number_format).get());
}
} // namespace
// static
MaybeHandle<JSPluralRules> JSPluralRules::InitializePluralRules(
Isolate* isolate, Handle<JSPluralRules> plural_rules,
Handle<Object> locales, Handle<Object> options_obj) {
// 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
Handle<JSObject> requested_locales;
ASSIGN_RETURN_ON_EXCEPTION(isolate, requested_locales,
Intl::CanonicalizeLocaleList(isolate, locales),
JSPluralRules);
// 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).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options_obj,
Object::ToObject(isolate, options_obj, "Intl.PluralRules"),
JSPluralRules);
}
// At this point, options_obj can either be a JSObject or a JSProxy only.
Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
// TODO(gsathya): This is currently done as part of the
// Intl::ResolveLocale call below. Fix this once resolveLocale is
// changed to not do the lookup.
//
// 5. Let matcher be ? GetOption(options, "localeMatcher", "string",
// « "lookup", "best fit" », "best fit").
// 6. Set opt.[[localeMatcher]] to matcher.
// 7. Let t be ? GetOption(options, "type", "string", « "cardinal",
// "ordinal" », "cardinal").
std::vector<const char*> values = {"cardinal", "ordinal"};
std::unique_ptr<char[]> type_str = nullptr;
const char* type_cstr = "cardinal";
Maybe<bool> found = Intl::GetStringOption(isolate, options, "type", values,
"Intl.PluralRules", &type_str);
MAYBE_RETURN(found, MaybeHandle<JSPluralRules>());
if (found.FromJust()) {
type_cstr = type_str.get();
}
// 8. Set pluralRules.[[Type]] to t.
Handle<String> type =
isolate->factory()->NewStringFromAsciiChecked(type_cstr);
plural_rules->set_type(*type);
// Note: The spec says we should do ResolveLocale after performing
// SetNumberFormatDigitOptions but we need the locale to create all
// the ICU data structures.
//
// This isn't observable so we aren't violating the spec.
// 11. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]],
// requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]],
// localeData).
Handle<JSObject> r;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, r,
Intl::ResolveLocale(isolate, "pluralrules", requested_locales, options),
JSPluralRules);
Handle<String> locale_str = isolate->factory()->locale_string();
Handle<Object> locale_obj = JSObject::GetDataProperty(r, locale_str);
// The locale has to be a string. Either a user provided
// canonicalized string or the default locale.
CHECK(locale_obj->IsString());
Handle<String> locale = Handle<String>::cast(locale_obj);
// 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
plural_rules->set_locale(*locale);
std::unique_ptr<icu::PluralRules> icu_plural_rules;
std::unique_ptr<icu::DecimalFormat> icu_decimal_format;
InitializeICUPluralRules(isolate, locale, type_cstr, &icu_plural_rules,
&icu_decimal_format);
CHECK_NOT_NULL(icu_plural_rules.get());
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>());
Handle<Managed<icu::PluralRules>> managed_plural_rules =
Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
std::move(icu_plural_rules));
plural_rules->set_icu_plural_rules(*managed_plural_rules);
Handle<Managed<icu::DecimalFormat>> managed_decimal_format =
Managed<icu::DecimalFormat>::FromUniquePtr(isolate, 0,
std::move(icu_decimal_format));
plural_rules->set_icu_decimal_format(*managed_decimal_format);
// 13. Return pluralRules.
return plural_rules;
}
MaybeHandle<String> JSPluralRules::ResolvePlural(
Isolate* isolate, Handle<JSPluralRules> plural_rules,
Handle<Object> number) {
icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
CHECK_NOT_NULL(icu_plural_rules);
icu::DecimalFormat* icu_decimal_format =
plural_rules->icu_decimal_format()->raw();
CHECK_NOT_NULL(icu_decimal_format);
// Currently, PluralRules doesn't implement all the options for rounding that
// the Intl spec provides; format and parse the number to round to the
// appropriate amount, then apply PluralRules.
//
// TODO(littledan): If a future ICU version supports an extended API to avoid
// this step, then switch to that API. Bug thread:
// http://bugs.icu-project.org/trac/ticket/12763
icu::UnicodeString rounded_string;
icu_decimal_format->format(number->Number(), rounded_string);
icu::Formattable formattable;
UErrorCode status = U_ZERO_ERROR;
icu_decimal_format->parse(rounded_string, formattable, status);
CHECK(U_SUCCESS(status));
double rounded = formattable.getDouble(status);
CHECK(U_SUCCESS(status));
icu::UnicodeString result = icu_plural_rules->select(rounded);
return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
}
namespace {
void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
Handle<Object> value, const char* key) {
Handle<String> key_str = isolate->factory()->NewStringFromAsciiChecked(key);
// This is a brand new JSObject that shouldn't already have the same
// key so this shouldn't fail.
CHECK(JSReceiver::CreateDataProperty(isolate, options, key_str, value,
kDontThrow)
.FromJust());
}
void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
int value, const char* key) {
Handle<Smi> value_smi(Smi::FromInt(value), isolate);
CreateDataPropertyForOptions(isolate, options, value_smi, key);
}
} // namespace
Handle<JSObject> JSPluralRules::ResolvedOptions(
Isolate* isolate, Handle<JSPluralRules> plural_rules) {
Handle<JSObject> options =
isolate->factory()->NewJSObject(isolate->object_function());
Handle<String> locale_value(plural_rules->locale(), isolate);
CreateDataPropertyForOptions(isolate, options, locale_value, "locale");
Handle<String> type_value(plural_rules->type(), isolate);
CreateDataPropertyForOptions(isolate, options, type_value, "type");
icu::DecimalFormat* icu_decimal_format =
plural_rules->icu_decimal_format()->raw();
CHECK_NOT_NULL(icu_decimal_format);
// This is a safe upcast as icu::DecimalFormat inherits from
// icu::NumberFormat.
icu::NumberFormat* icu_number_format =
static_cast<icu::NumberFormat*>(icu_decimal_format);
int min_int_digits = icu_number_format->getMinimumIntegerDigits();
CreateDataPropertyForOptions(isolate, options, min_int_digits,
"minimumIntegerDigits");
int min_fraction_digits = icu_number_format->getMinimumFractionDigits();
CreateDataPropertyForOptions(isolate, options, min_fraction_digits,
"minimumFractionDigits");
int max_fraction_digits = icu_number_format->getMaximumFractionDigits();
CreateDataPropertyForOptions(isolate, options, max_fraction_digits,
"maximumFractionDigits");
if (icu_decimal_format->areSignificantDigitsUsed()) {
int min_significant_digits =
icu_decimal_format->getMinimumSignificantDigits();
CreateDataPropertyForOptions(isolate, options, min_significant_digits,
"minimumSignificantDigits");
int max_significant_digits =
icu_decimal_format->getMaximumSignificantDigits();
CreateDataPropertyForOptions(isolate, options, max_significant_digits,
"maximumSignificantDigits");
}
// 6. Let pluralCategories be a List of Strings representing the
// possible results of PluralRuleSelect for the selected locale pr.
icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
CHECK_NOT_NULL(icu_plural_rules);
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::StringEnumeration> categories(
icu_plural_rules->getKeywords(status));
CHECK(U_SUCCESS(status));
int32_t count = categories->count(status);
CHECK(U_SUCCESS(status));
Handle<FixedArray> plural_categories =
isolate->factory()->NewFixedArray(count);
for (int32_t i = 0; i < count; i++) {
const icu::UnicodeString* category = categories->snext(status);
CHECK(U_SUCCESS(status));
if (category == nullptr) break;
std::string keyword;
Handle<String> value = isolate->factory()->NewStringFromAsciiChecked(
category->toUTF8String(keyword).data());
plural_categories->set(i, *value);
}
// 7. Perform ! CreateDataProperty(options, "pluralCategories",
// CreateArrayFromList(pluralCategories)).
Handle<JSArray> plural_categories_value =
isolate->factory()->NewJSArrayWithElements(plural_categories);
CreateDataPropertyForOptions(isolate, options, plural_categories_value,
"pluralCategories");
return options;
}
} // namespace internal
} // namespace v8

View File

@ -0,0 +1,69 @@
// 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
#ifndef V8_OBJECTS_JS_PLURAL_RULES_H_
#define V8_OBJECTS_JS_PLURAL_RULES_H_
#include "src/heap/factory.h"
#include "src/isolate.h"
#include "src/objects.h"
#include "src/objects/intl-objects.h"
#include "src/objects/managed.h"
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
class JSPluralRules : public JSObject {
public:
V8_WARN_UNUSED_RESULT static MaybeHandle<JSPluralRules> InitializePluralRules(
Isolate* isolate, Handle<JSPluralRules> plural_rules,
Handle<Object> locales, Handle<Object> options);
static Handle<JSObject> ResolvedOptions(Isolate* isolate,
Handle<JSPluralRules> plural_rules);
V8_WARN_UNUSED_RESULT static MaybeHandle<String> ResolvePlural(
Isolate* isolate, Handle<JSPluralRules> plural_rules,
Handle<Object> number);
DECL_CAST(JSPluralRules)
DECL_PRINTER(JSPluralRules)
DECL_VERIFIER(JSPluralRules)
// Layout description.
#define JS_PLURAL_RULES_FIELDS(V) \
V(kLocaleOffset, kPointerSize) \
/* In the future, this can be an enum, \
and not a string. */ \
V(kTypeOffset, kPointerSize) \
V(kICUPluralRulesOffset, kPointerSize) \
V(kICUDecimalFormatOffset, kPointerSize) \
/* Total size. */ \
V(kSize, 0)
DEFINE_FIELD_OFFSET_CONSTANTS(JSObject::kHeaderSize, JS_PLURAL_RULES_FIELDS)
#undef JS_PLURAL_RULES_FIELDS
DECL_ACCESSORS(locale, String)
DECL_ACCESSORS(type, String)
DECL_ACCESSORS(icu_plural_rules, Managed<icu::PluralRules>)
DECL_ACCESSORS(icu_decimal_format, Managed<icu::DecimalFormat>)
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(JSPluralRules);
};
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_JS_PLURAL_RULES_H_

View File

@ -20,6 +20,7 @@
#include "src/messages.h"
#include "src/objects/intl-objects-inl.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-plural-rules-inl.h"
#include "src/objects/managed.h"
#include "src/runtime/runtime-utils.h"
#include "src/utils.h"
@ -60,9 +61,13 @@ RUNTIME_FUNCTION(Runtime_GetNumberOption) {
CONVERT_SMI_ARG_CHECKED(min, 2);
CONVERT_SMI_ARG_CHECKED(max, 3);
CONVERT_SMI_ARG_CHECKED(fallback, 4);
RETURN_RESULT_OR_FAILURE(
isolate,
Intl::GetNumberOption(isolate, options, property, min, max, fallback));
Maybe<int> num =
Intl::GetNumberOption(isolate, options, property, min, max, fallback);
if (num.IsNothing()) {
return ReadOnlyRoots(isolate).exception();
}
return Smi::FromInt(num.FromJust());
}
RUNTIME_FUNCTION(Runtime_DefaultNumberOption) {
@ -73,9 +78,13 @@ RUNTIME_FUNCTION(Runtime_DefaultNumberOption) {
CONVERT_SMI_ARG_CHECKED(max, 2);
CONVERT_SMI_ARG_CHECKED(fallback, 3);
CONVERT_ARG_HANDLE_CHECKED(String, property, 4);
RETURN_RESULT_OR_FAILURE(
isolate,
Intl::DefaultNumberOption(isolate, value, min, max, fallback, property));
Maybe<int> num =
Intl::DefaultNumberOption(isolate, value, min, max, fallback, property);
if (num.IsNothing()) {
return ReadOnlyRoots(isolate).exception();
}
return Smi::FromInt(num.FromJust());
}
// ECMA 402 6.2.3
@ -275,80 +284,52 @@ RUNTIME_FUNCTION(Runtime_InternalCompare) {
return *Intl::InternalCompare(isolate, collator_holder, string1, string2);
}
RUNTIME_FUNCTION(Runtime_CreatePluralRules) {
RUNTIME_FUNCTION(Runtime_PluralRulesResolvedOptions) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, plural_rules_obj, 0);
CONVERT_ARG_HANDLE_CHECKED(String, locale, 0);
CONVERT_ARG_HANDLE_CHECKED(JSObject, options, 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, resolved, 2);
// 3. If pr does not have an [[InitializedPluralRules]] internal
// slot, throw a TypeError exception.
if (!plural_rules_obj->IsJSPluralRules()) {
Handle<String> method_str = isolate->factory()->NewStringFromStaticChars(
"Intl.PluralRules.prototype.resolvedOptions");
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
method_str, plural_rules_obj));
}
Handle<JSFunction> constructor(
isolate->native_context()->intl_plural_rules_function(), isolate);
Handle<JSPluralRules> plural_rules =
Handle<JSPluralRules>::cast(plural_rules_obj);
Handle<JSObject> local_object;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, local_object,
JSObject::New(constructor, constructor));
// Set pluralRules as internal field of the resulting JS object.
icu::PluralRules* plural_rules;
icu::DecimalFormat* decimal_format;
PluralRules::InitializePluralRules(isolate, locale, options, resolved,
&plural_rules, &decimal_format);
CHECK_NOT_NULL(plural_rules);
CHECK_NOT_NULL(decimal_format);
local_object->SetEmbedderField(0, reinterpret_cast<Smi*>(plural_rules));
local_object->SetEmbedderField(1, reinterpret_cast<Smi*>(decimal_format));
Handle<Object> wrapper = isolate->global_handles()->Create(*local_object);
GlobalHandles::MakeWeak(wrapper.location(), wrapper.location(),
PluralRules::DeletePluralRules,
WeakCallbackType::kInternalFields);
return *local_object;
return *JSPluralRules::ResolvedOptions(isolate, plural_rules);
}
RUNTIME_FUNCTION(Runtime_PluralRulesSelect) {
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSObject, plural_rules_holder, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, plural_rules_obj, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, number, 1);
icu::PluralRules* plural_rules =
PluralRules::UnpackPluralRules(plural_rules_holder);
CHECK_NOT_NULL(plural_rules);
// 3. If pr does not have an [[InitializedPluralRules]] internal
// slot, throw a TypeError exception.
if (!plural_rules_obj->IsJSPluralRules()) {
Handle<String> method_str = isolate->factory()->NewStringFromStaticChars(
"Intl.PluralRules.prototype.select");
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
method_str, plural_rules_obj));
}
icu::DecimalFormat* number_format =
PluralRules::UnpackNumberFormat(plural_rules_holder);
CHECK_NOT_NULL(number_format);
Handle<JSPluralRules> plural_rules =
Handle<JSPluralRules>::cast(plural_rules_obj);
// Currently, PluralRules doesn't implement all the options for rounding that
// the Intl spec provides; format and parse the number to round to the
// appropriate amount, then apply PluralRules.
//
// TODO(littledan): If a future ICU version supports an extended API to avoid
// this step, then switch to that API. Bug thread:
// http://bugs.icu-project.org/trac/ticket/12763
icu::UnicodeString rounded_string;
number_format->format(number->Number(), rounded_string);
// 4. Return ? ResolvePlural(pr, n).
icu::Formattable formattable;
UErrorCode status = U_ZERO_ERROR;
number_format->parse(rounded_string, formattable, status);
if (!U_SUCCESS(status)) return isolate->ThrowIllegalOperation();
double rounded = formattable.getDouble(status);
if (!U_SUCCESS(status)) return isolate->ThrowIllegalOperation();
icu::UnicodeString result = plural_rules->select(rounded);
return *isolate->factory()
->NewStringFromTwoByte(Vector<const uint16_t>(
reinterpret_cast<const uint16_t*>(result.getBuffer()),
result.length()))
.ToHandleChecked();
RETURN_RESULT_OR_FAILURE(
isolate, JSPluralRules::ResolvePlural(isolate, plural_rules, number));
}
RUNTIME_FUNCTION(Runtime_CreateBreakIterator) {

View File

@ -214,7 +214,6 @@ namespace internal {
F(CreateDateTimeFormat, 3, 1) \
F(CreateNumberFormat, 3, 1) \
F(DefineWEProperty, 3, 1) \
F(CreatePluralRules, 3, 1) \
F(CurrencyDigits, 1, 1) \
F(DateCacheVersion, 0, 1) \
F(DefaultNumberOption, 5, 1) \
@ -226,6 +225,7 @@ namespace internal {
F(IsInitializedIntlObjectOfType, 2, 1) \
F(IsWellFormedCurrencyCode, 1, 1) \
F(MarkAsInitializedIntlObjectOfType, 2, 1) \
F(PluralRulesResolvedOptions, 1, 1) \
F(PluralRulesSelect, 2, 1) \
F(StringToLowerCaseIntl, 1, 1) \
F(StringToUpperCaseIntl, 1, 1)

View File

@ -161,14 +161,15 @@ INSTANCE_TYPES = {
1083: "JS_DATA_VIEW_TYPE",
1084: "JS_INTL_LIST_FORMAT_TYPE",
1085: "JS_INTL_LOCALE_TYPE",
1086: "JS_INTL_RELATIVE_TIME_FORMAT_TYPE",
1087: "WASM_GLOBAL_TYPE",
1088: "WASM_INSTANCE_TYPE",
1089: "WASM_MEMORY_TYPE",
1090: "WASM_MODULE_TYPE",
1091: "WASM_TABLE_TYPE",
1092: "JS_BOUND_FUNCTION_TYPE",
1093: "JS_FUNCTION_TYPE",
1086: "JS_INTL_PLURAL_RULES_TYPE",
1087: "JS_INTL_RELATIVE_TIME_FORMAT_TYPE",
1088: "WASM_GLOBAL_TYPE",
1089: "WASM_INSTANCE_TYPE",
1090: "WASM_MEMORY_TYPE",
1091: "WASM_MODULE_TYPE",
1092: "WASM_TABLE_TYPE",
1093: "JS_BOUND_FUNCTION_TYPE",
1094: "JS_FUNCTION_TYPE",
}
# List of known V8 maps.