Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Implementation of the Intl.PluralRules proposal. */
#include "builtin/intl/PluralRules.h"
#include "mozilla/Assertions.h"
#include "mozilla/Casting.h"
#include "builtin/Array.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/NumberFormat.h"
#include "builtin/intl/ScopedICUObject.h"
#include "gc/FreeOp.h"
#include "js/CharacterEncoding.h"
#include "js/PropertySpec.h"
#include "unicode/uenum.h"
#include "unicode/uloc.h"
#include "unicode/unumberformatter.h"
#include "unicode/upluralrules.h"
#include "unicode/utypes.h"
#include "vm/GlobalObject.h"
#include "vm/JSContext.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/StringType.h"
#include "vm/JSObject-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
using mozilla::AssertedCast;
using js::intl::CallICU;
using js::intl::IcuLocale;
const JSClassOps PluralRulesObject::classOps_ = {
nullptr, // addProperty
nullptr, // delProperty
nullptr, // enumerate
nullptr, // newEnumerate
nullptr, // resolve
nullptr, // mayResolve
PluralRulesObject::finalize, // finalize
nullptr, // call
nullptr, // hasInstance
nullptr, // construct
nullptr, // trace
};
const JSClass PluralRulesObject::class_ = {
js_Object_str,
JSCLASS_HAS_RESERVED_SLOTS(PluralRulesObject::SLOT_COUNT) |
JSCLASS_HAS_CACHED_PROTO(JSProto_PluralRules) |
JSCLASS_FOREGROUND_FINALIZE,
&PluralRulesObject::classOps_, &PluralRulesObject::classSpec_};
const JSClass& PluralRulesObject::protoClass_ = PlainObject::class_;
static bool pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setString(cx->names().PluralRules);
return true;
}
static const JSFunctionSpec pluralRules_static_methods[] = {
JS_SELF_HOSTED_FN("supportedLocalesOf",
"Intl_PluralRules_supportedLocalesOf", 1, 0),
JS_FS_END};
static const JSFunctionSpec pluralRules_methods[] = {
JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0,
0),
JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
JS_FN(js_toSource_str, pluralRules_toSource, 0, 0), JS_FS_END};
static const JSPropertySpec pluralRules_properties[] = {
JS_STRING_SYM_PS(toStringTag, "Intl.PluralRules", JSPROP_READONLY),
JS_PS_END};
static bool PluralRules(JSContext* cx, unsigned argc, Value* vp);
const ClassSpec PluralRulesObject::classSpec_ = {
GenericCreateConstructor<PluralRules, 0, gc::AllocKind::FUNCTION>,
GenericCreatePrototype<PluralRulesObject>,
pluralRules_static_methods,
nullptr,
pluralRules_methods,
pluralRules_properties,
nullptr,
ClassSpec::DontDefineConstructor};
/**
* PluralRules constructor.
* Spec: ECMAScript 402 API, PluralRules, 13.2.1
*/
static bool PluralRules(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
// Step 1.
if (!ThrowIfNotConstructing(cx, args, "Intl.PluralRules")) {
return false;
}
// Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PluralRules,
&proto)) {
return false;
}
Rooted<PluralRulesObject*> pluralRules(cx);
pluralRules = NewObjectWithClassProto<PluralRulesObject>(cx, proto);
if (!pluralRules) {
return false;
}
HandleValue locales = args.get(0);
HandleValue options = args.get(1);
// Step 3.
if (!intl::InitializeObject(cx, pluralRules,
cx->names().InitializePluralRules, locales,
options)) {
return false;
}
args.rval().setObject(*pluralRules);
return true;
}
void js::PluralRulesObject::finalize(JSFreeOp* fop, JSObject* obj) {
MOZ_ASSERT(fop->onMainThread());
auto* pluralRules = &obj->as<PluralRulesObject>();
UPluralRules* pr = pluralRules->getPluralRules();
UNumberFormatter* nf = pluralRules->getNumberFormatter();
UFormattedNumber* formatted = pluralRules->getFormattedNumber();
if (pr) {
intl::RemoveICUCellMemory(
fop, obj, PluralRulesObject::UPluralRulesEstimatedMemoryUse);
}
if (nf) {
intl::RemoveICUCellMemory(
fop, obj, PluralRulesObject::UNumberFormatterEstimatedMemoryUse);
// UFormattedNumber memory tracked as part of UNumberFormatter.
}
if (pr) {
uplrules_close(pr);
}
if (nf) {
unumf_close(nf);
}
if (formatted) {
unumf_closeResult(formatted);
}
}
/**
* This creates a new UNumberFormatter with calculated digit formatting
* properties for PluralRules.
*
* This is similar to NewUNumberFormatter but doesn't allow for currency or
* percent types.
*/
static UNumberFormatter* NewUNumberFormatterForPluralRules(
JSContext* cx, Handle<PluralRulesObject*> pluralRules) {
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
if (!internals) {
return nullptr;
}
RootedValue value(cx);
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
return nullptr;
}
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
if (!locale) {
return nullptr;
}
intl::NumberFormatterSkeleton skeleton(cx);
bool hasMinimumSignificantDigits;
if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
&hasMinimumSignificantDigits)) {
return nullptr;
}
if (hasMinimumSignificantDigits) {
if (!GetProperty(cx, internals, internals,
cx->names().minimumSignificantDigits, &value)) {
return nullptr;
}
uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals,
cx->names().maximumSignificantDigits, &value)) {
return nullptr;
}
uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
if (!skeleton.significantDigits(minimumSignificantDigits,
maximumSignificantDigits)) {
return nullptr;
}
} else {
if (!GetProperty(cx, internals, internals,
cx->names().minimumFractionDigits, &value)) {
return nullptr;
}
uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals,
cx->names().maximumFractionDigits, &value)) {
return nullptr;
}
uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!skeleton.fractionDigits(minimumFractionDigits,
maximumFractionDigits)) {
return nullptr;
}
}
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value)) {
return nullptr;
}
uint32_t minimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!skeleton.integerWidth(minimumIntegerDigits)) {
return nullptr;
}
if (!skeleton.roundingModeHalfUp()) {
return nullptr;
}
return skeleton.toFormatter(cx, locale.get());
}
static UFormattedNumber* NewUFormattedNumberForPluralRules(JSContext* cx) {
UErrorCode status = U_ZERO_ERROR;
UFormattedNumber* formatted = unumf_openResult(&status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
}
return formatted;
}
/**
* Returns a new UPluralRules with the locale and type options of the given
* PluralRules.
*/
static UPluralRules* NewUPluralRules(JSContext* cx,
Handle<PluralRulesObject*> pluralRules) {
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
if (!internals) {
return nullptr;
}
RootedValue value(cx);
if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
return nullptr;
}
UniqueChars locale = intl::EncodeLocale(cx, value.toString());
if (!locale) {
return nullptr;
}
if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
return nullptr;
}
UPluralType category;
{
JSLinearString* type = value.toString()->ensureLinear(cx);
if (!type) {
return nullptr;
}
if (StringEqualsLiteral(type, "cardinal")) {
category = UPLURAL_TYPE_CARDINAL;
} else {
MOZ_ASSERT(StringEqualsLiteral(type, "ordinal"));
category = UPLURAL_TYPE_ORDINAL;
}
}
UErrorCode status = U_ZERO_ERROR;
UPluralRules* pr =
uplrules_openForType(IcuLocale(locale.get()), category, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return nullptr;
}
return pr;
}
bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
Rooted<PluralRulesObject*> pluralRules(
cx, &args[0].toObject().as<PluralRulesObject>());
double x = args[1].toNumber();
// Obtain a cached UPluralRules object.
UPluralRules* pr = pluralRules->getPluralRules();
if (!pr) {
pr = NewUPluralRules(cx, pluralRules);
if (!pr) {
return false;
}
pluralRules->setPluralRules(pr);
intl::AddICUCellMemory(pluralRules,
PluralRulesObject::UPluralRulesEstimatedMemoryUse);
}
// Obtain a cached UNumberFormat object.
UNumberFormatter* nf = pluralRules->getNumberFormatter();
if (!nf) {
nf = NewUNumberFormatterForPluralRules(cx, pluralRules);
if (!nf) {
return false;
}
pluralRules->setNumberFormatter(nf);
intl::AddICUCellMemory(
pluralRules, PluralRulesObject::UNumberFormatterEstimatedMemoryUse);
}
// Obtain a cached UFormattedNumber object.
UFormattedNumber* formatted = pluralRules->getFormattedNumber();
if (!formatted) {
formatted = NewUFormattedNumberForPluralRules(cx);
if (!formatted) {
return false;
}
pluralRules->setFormattedNumber(formatted);
// UFormattedNumber memory tracked as part of UNumberFormatter.
}
UErrorCode status = U_ZERO_ERROR;
unumf_formatDouble(nf, x, formatted, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
JSString* str = CallICU(
cx, [pr, formatted](UChar* chars, int32_t size, UErrorCode* status) {
return uplrules_selectFormatted(pr, formatted, chars, size, status);
});
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
Rooted<PluralRulesObject*> pluralRules(
cx, &args[0].toObject().as<PluralRulesObject>());
// Obtain a cached UPluralRules object.
UPluralRules* pr = pluralRules->getPluralRules();
if (!pr) {
pr = NewUPluralRules(cx, pluralRules);
if (!pr) {
return false;
}
pluralRules->setPluralRules(pr);
intl::AddICUCellMemory(pluralRules,
PluralRulesObject::UPluralRulesEstimatedMemoryUse);
}
UErrorCode status = U_ZERO_ERROR;
UEnumeration* ue = uplrules_getKeywords(pr, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
ScopedICUObject<UEnumeration, uenum_close> closeEnum(ue);
RootedObject res(cx, NewDenseEmptyArray(cx));
if (!res) {
return false;
}
do {
int32_t catSize;
const char* cat = uenum_next(ue, &catSize, &status);
if (U_FAILURE(status)) {
intl::ReportInternalError(cx);
return false;
}
if (!cat) {
break;
}
MOZ_ASSERT(catSize >= 0);
JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
if (!str) {
return false;
}
if (!NewbornArrayPush(cx, res, StringValue(str))) {
return false;
}
} while (true);
args.rval().setObject(*res);
return true;
}