// 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).
|
// TODO(jkummerow): Port ResolveLocale, then use the C++ version of
|
// CanonicalizeLocaleList here.
|
Handle<JSObject> requested_locales;
|
ASSIGN_RETURN_ON_EXCEPTION(isolate, requested_locales,
|
Intl::CanonicalizeLocaleListJS(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
|