Commit 99e89636 authored by Josh Wolfe's avatar Josh Wolfe Committed by Commit Bot

[intl] Implement Intl.PluralRules behind --harmony-plural-rules

This feature is a stage 3 proposal implemented as a
wrapper around ICU that categorizes singular/plural/etc
grammatical forms based on a number and locale.

Based on littledan's work started here:
https://codereview.chromium.org/2736543002/

Bug: v8:5601
Cq-Include-Trybots: master.tryserver.v8:v8_linux_noi18n_rel_ng
Change-Id: I4107cd28be72413ec43aa1ff0f4fe6e181a290f4
Reviewed-on: https://chromium-review.googlesource.com/562298
Commit-Queue: Josh Wolfe <jwolfe@igalia.com>
Reviewed-by: 's avatarDaniel Ehrenberg <littledan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47485}
parent 6e13ca6d
......@@ -4100,6 +4100,24 @@ void Bootstrapper::ExportFromRuntime(Isolate* isolate,
}
}
}
#ifdef V8_INTL_SUPPORT
{ // I n t l P l u r a l R u l e s
Handle<JSObject> plural_rules_prototype =
factory->NewJSObject(isolate->object_function(), TENURED);
// Install the @@toStringTag property on the {prototype}.
JSObject::AddProperty(
plural_rules_prototype, factory->to_string_tag_symbol(),
factory->Object_string(),
static_cast<PropertyAttributes>(DONT_ENUM | READ_ONLY));
Handle<JSFunction> plural_rules_constructor = InstallFunction(
container, "PluralRules", JS_OBJECT_TYPE, PluralRules::kSize,
plural_rules_prototype, Builtins::kIllegal);
JSObject::AddProperty(plural_rules_prototype, factory->constructor_string(),
plural_rules_constructor, DONT_ENUM);
native_context->set_intl_plural_rules_function(*plural_rules_constructor);
}
#endif // V8_INTL_SUPPORT
}
......@@ -4266,6 +4284,20 @@ void Genesis::InitializeGlobal_harmony_number_format_to_parts() {
name);
}
void Genesis::InitializeGlobal_harmony_plural_rules() {
if (!FLAG_harmony_plural_rules) return;
Handle<JSFunction> plural_rules(
native_context()->intl_plural_rules_function());
Handle<JSObject> intl = Handle<JSObject>::cast(
JSReceiver::GetProperty(
Handle<JSReceiver>(native_context()->global_object()),
factory()->InternalizeUtf8String("Intl"))
.ToHandleChecked());
JSObject::AddProperty(intl, factory()->InternalizeUtf8String("PluralRules"),
plural_rules, DONT_ENUM);
}
#endif // V8_INTL_SUPPORT
Handle<JSFunction> Genesis::CreateArrayBuffer(Handle<String> name,
......
......@@ -277,6 +277,7 @@ enum ContextLookupFlags {
V(INTL_NUMBER_FORMAT_FUNCTION_INDEX, JSFunction, \
intl_number_format_function) \
V(INTL_COLLATOR_FUNCTION_INDEX, JSFunction, intl_collator_function) \
V(INTL_PLURAL_RULES_FUNCTION_INDEX, JSFunction, intl_plural_rules_function) \
V(INTL_V8_BREAK_ITERATOR_FUNCTION_INDEX, JSFunction, \
intl_v8_break_iterator_function) \
V(JS_ARRAY_PACKED_SMI_ELEMENTS_MAP_INDEX, Map, \
......
......@@ -197,11 +197,11 @@ DEFINE_IMPLICATION(es_staging, harmony)
V(harmony_promise_finally, "harmony Promise.prototype.finally")
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(harmony_number_format_to_parts, \
"Intl.NumberFormat.prototype." \
"formatToParts")
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(harmony_number_format_to_parts, \
"Intl.NumberFormat.prototype.formatToParts") \
V(harmony_plural_rules, "Intl.PluralRules")
#else
#define HARMONY_INPROGRESS(V) HARMONY_INPROGRESS_BASE(V)
#endif
......
......@@ -24,6 +24,7 @@ var GlobalIntl = global.Intl;
var GlobalIntlDateTimeFormat = GlobalIntl.DateTimeFormat;
var GlobalIntlNumberFormat = GlobalIntl.NumberFormat;
var GlobalIntlCollator = GlobalIntl.Collator;
var GlobalIntlPluralRules = utils.ImportNow("PluralRules");
var GlobalIntlv8BreakIterator = GlobalIntl.v8BreakIterator;
var GlobalNumber = global.Number;
var GlobalRegExp = global.RegExp;
......@@ -32,6 +33,7 @@ var IntlFallbackSymbol = utils.ImportNow("intl_fallback_symbol");
var InternalArray = utils.InternalArray;
var MaxSimple;
var ObjectHasOwnProperty = global.Object.prototype.hasOwnProperty;
var ObjectKeys = global.Object.keys;
var patternSymbol = utils.ImportNow("intl_pattern_symbol");
var resolvedSymbol = utils.ImportNow("intl_resolved_symbol");
var StringSubstr = GlobalString.prototype.substr;
......@@ -124,7 +126,8 @@ var AVAILABLE_LOCALES = {
'collator': UNDEFINED,
'numberformat': UNDEFINED,
'dateformat': UNDEFINED,
'breakiterator': UNDEFINED
'breakiterator': UNDEFINED,
'pluralrules': UNDEFINED,
};
/**
......@@ -190,7 +193,7 @@ var SERVICE_RE = UNDEFINED;
function GetServiceRE() {
if (IS_UNDEFINED(SERVICE_RE)) {
SERVICE_RE =
new GlobalRegExp('^(collator|numberformat|dateformat|breakiterator)$');
new GlobalRegExp('^(' + %_Call(ArrayJoin, ObjectKeys(AVAILABLE_LOCALES), '|') + ')$');
}
return SERVICE_RE;
}
......@@ -1072,6 +1075,108 @@ function compare(collator, x, y) {
AddBoundMethod(GlobalIntlCollator, 'compare', compare, 2, 'collator', 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 = {};
}
var getOption = getGetOption(options, 'pluralrules');
var locale = resolveLocale('pluralrules', locales, options);
var internalOptions = {};
defineWEProperty(internalOptions, 'type', getOption(
'type', 'string', ['cardinal', 'ordinal'], 'cardinal'));
SetNumberFormatDigitOptions(internalOptions, options, 0, 3);
var requestedLocale = locale.locale;
var resolved = %object_define_properties({}, {
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, 'pluralrules');
pluralRules[resolvedSymbol] = resolved;
return pluralRules;
}
%SetCode(GlobalIntlPluralRules, PluralRulesConstructor);
DEFINE_METHOD(
GlobalIntlPluralRules.prototype,
resolvedOptions() {
if (!%IsInitializedIntlObjectOfType(this, 'pluralrules')) {
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',
this[resolvedSymbol].pluralCategories);
return result;
}
);
DEFINE_METHOD(
GlobalIntlPluralRules,
supportedLocalesOf(locales) {
return supportedLocalesOf('pluralrules', locales, arguments[1]);
}
);
DEFINE_METHOD(
GlobalIntlPluralRules.prototype,
select(value) {
if (!%IsInitializedIntlObjectOfType(this, 'pluralrules')) {
throw %make_type_error(kIncompatibleMethodReceiver,
'Intl.PluralRules.prototype.select',
this);
}
return %PluralRulesSelect(this, TO_NUMBER(value) + 0);
}
);
/**
* Verifies that the input is a well-formed ISO 4217 currency code.
* Don't uppercase to test. It could convert invalid code into a valid one.
......
This diff is collapsed.
......@@ -16,6 +16,7 @@ namespace U_ICU_NAMESPACE {
class BreakIterator;
class Collator;
class DecimalFormat;
class PluralRules;
class SimpleDateFormat;
}
......@@ -98,6 +99,43 @@ class Collator {
Collator();
};
class PluralRules {
public:
// Create a PluralRules and DecimalFormat for the specificied locale and
// options. Returns false on an ICU failure.
static bool 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(Isolate* isolate,
Handle<JSObject> obj);
// Unpacks NumberFormat object from corresponding JavaScript PluralRUles
// object.
static icu::DecimalFormat* UnpackNumberFormat(Isolate* isolate,
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
......
......@@ -34,6 +34,7 @@
#include "unicode/locid.h"
#include "unicode/numfmt.h"
#include "unicode/numsys.h"
#include "unicode/plurrule.h"
#include "unicode/rbbi.h"
#include "unicode/smpdtfmt.h"
#include "unicode/timezone.h"
......@@ -109,6 +110,15 @@ RUNTIME_FUNCTION(Runtime_AvailableLocalesOf) {
available_locales = icu::DateFormat::getAvailableLocales(count);
} else if (service->IsUtf8EqualTo(CStrVector("breakiterator"))) {
available_locales = icu::BreakIterator::getAvailableLocales(count);
} else if (service->IsUtf8EqualTo(CStrVector("pluralrules"))) {
// TODO(littledan): For PluralRules, filter out locales that
// don't support PluralRules.
// PluralRules is missing an appropriate getAvailableLocales method,
// so we should filter from all locales, but it's not clear how; see
// https://ssl.icu-project.org/trac/ticket/12756
available_locales = icu::Locale::getAvailableLocales(count);
} else {
UNREACHABLE();
}
UErrorCode error = U_ZERO_ERROR;
......@@ -626,6 +636,83 @@ RUNTIME_FUNCTION(Runtime_InternalCompare) {
return *isolate->factory()->NewNumberFromInt(result);
}
RUNTIME_FUNCTION(Runtime_CreatePluralRules) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(String, locale, 0);
CONVERT_ARG_HANDLE_CHECKED(JSObject, options, 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, resolved, 2);
Handle<JSFunction> constructor(
isolate->native_context()->intl_plural_rules_function());
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;
bool success = PluralRules::InitializePluralRules(
isolate, locale, options, resolved, &plural_rules, &decimal_format);
if (!success) return isolate->ThrowIllegalOperation();
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;
}
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, number, 1);
icu::PluralRules* plural_rules =
PluralRules::UnpackPluralRules(isolate, plural_rules_holder);
CHECK_NOT_NULL(plural_rules);
icu::DecimalFormat* number_format =
PluralRules::UnpackNumberFormat(isolate, plural_rules_holder);
CHECK_NOT_NULL(number_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;
number_format->format(number->Number(), rounded_string);
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*>(
icu::toUCharPtr(result.getBuffer())),
result.length()))
.ToHandleChecked();
}
RUNTIME_FUNCTION(Runtime_CreateBreakIterator) {
HandleScope scope(isolate);
......
......@@ -262,6 +262,8 @@ namespace internal {
F(CurrencyDigits, 1, 1) \
F(CreateCollator, 3, 1) \
F(InternalCompare, 3, 1) \
F(CreatePluralRules, 3, 1) \
F(PluralRulesSelect, 2, 1) \
F(CreateBreakIterator, 3, 1) \
F(BreakIteratorAdoptText, 2, 1) \
F(BreakIteratorFirst, 1, 1) \
......
// Copyright 2017 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.
// Flags: --harmony-plural-rules
if (this.Intl) {
var pr;
var suffixes;
function format(n) {
return "" + n + suffixes[pr.select(n)];
}
// These English examples illustrate the purpose of the PluralRules class.
pr = new Intl.PluralRules("en-US");
suffixes = {
one: " day",
other: " days",
};
assertEquals("0 days", format(0));
assertEquals("0.5 days", format(0.5));
assertEquals("1 day", format(1));
assertEquals("1.5 days", format(1.5));
assertEquals("2 days", format(2));
pr = new Intl.PluralRules("en-US", {type: "ordinal"});
suffixes = {
one: "st",
two: "nd",
few: "rd",
other: "th",
};
assertEquals("0th", format(0));
assertEquals("1st", format(1));
assertEquals("2nd", format(2));
assertEquals("3rd", format(3));
assertEquals("4th", format(4));
assertEquals("11th", format(11));
assertEquals("21st", format(21));
assertEquals("103rd", format(103));
// Arabic can cause every possible return value from select()
pr = new Intl.PluralRules("ar");
suffixes = null;
assertEquals("zero", pr.select(0));
assertEquals("one", pr.select(1));
assertEquals("two", pr.select(2));
assertEquals("few", pr.select(3));
assertEquals("many", pr.select(11));
assertEquals("other", pr.select(100));
assertEquals("other", pr.select(1.5));
}
......@@ -370,7 +370,7 @@
'language/module-code/namespace/internals/define-own-property': [FAIL],
# https://bugs.chromium.org/p/v8/issues/detail?id=5601
'intl402/PluralRules/*': [SKIP],
'intl402/PluralRules/*': ['--harmony-plural-rules'],
# SharedArrayBuffer tests that require flags
'built-ins/SharedArrayBuffer/*': ['--harmony-sharedarraybuffer'],
......
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