mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 08:18:41 +09:00
Issue #2046 - Move Intl.PluralRules functionality into builtin/intl/PluralRules.*
This commit is contained in:
parent
c981fb13ea
commit
ed316832bf
6 changed files with 567 additions and 491 deletions
|
|
@ -27,6 +27,7 @@
|
|||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/ICUHeader.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
#include "builtin/intl/PluralRules.h"
|
||||
#include "builtin/intl/ScopedICUObject.h"
|
||||
#include "ds/Sort.h"
|
||||
#include "vm/DateTime.h"
|
||||
|
|
@ -52,449 +53,6 @@ using js::intl::IcuLocale;
|
|||
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
|
||||
using js::intl::StringsAreEqual;
|
||||
|
||||
/**************** PluralRules *****************/
|
||||
|
||||
static void pluralRules_finalize(FreeOp* fop, JSObject* obj);
|
||||
|
||||
static const uint32_t UPLURAL_RULES_SLOT = 0;
|
||||
static const uint32_t PLURAL_RULES_SLOTS_COUNT = 1;
|
||||
|
||||
static const ClassOps PluralRulesClassOps = {
|
||||
nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
pluralRules_finalize
|
||||
};
|
||||
|
||||
static const Class PluralRulesClass = {
|
||||
js_Object_str,
|
||||
JSCLASS_HAS_RESERVED_SLOTS(PLURAL_RULES_SLOTS_COUNT) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&PluralRulesClassOps
|
||||
};
|
||||
|
||||
#if JS_HAS_TOSOURCE
|
||||
static bool
|
||||
pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setString(cx->names().PluralRules);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
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),
|
||||
#if JS_HAS_TOSOURCE
|
||||
JS_FN(js_toSource_str, pluralRules_toSource, 0, 0),
|
||||
#endif
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
/**
|
||||
* PluralRules constructor.
|
||||
* Spec: ECMAScript 402 API, PluralRules, 1.1
|
||||
*/
|
||||
static bool
|
||||
PluralRules(JSContext* cx, const CallArgs& args, bool construct)
|
||||
{
|
||||
RootedObject obj(cx);
|
||||
|
||||
if (!construct) {
|
||||
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
|
||||
if (!intl)
|
||||
return false;
|
||||
RootedValue self(cx, args.thisv());
|
||||
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
|
||||
obj = ToObject(cx, self);
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
bool extensible;
|
||||
if (!IsExtensible(cx, obj, &extensible))
|
||||
return false;
|
||||
if (!extensible)
|
||||
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
|
||||
} else {
|
||||
construct = true;
|
||||
}
|
||||
}
|
||||
if (construct) {
|
||||
RootedObject proto(cx, GlobalObject::getOrCreatePluralRulesPrototype(cx, cx->global()));
|
||||
if (!proto)
|
||||
return false;
|
||||
obj = NewObjectWithGivenProto(cx, &PluralRulesClass, proto);
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
obj->as<NativeObject>().setReservedSlot(UPLURAL_RULES_SLOT, PrivateValue(nullptr));
|
||||
}
|
||||
|
||||
RootedValue locales(cx, args.get(0));
|
||||
RootedValue options(cx, args.get(1));
|
||||
|
||||
if (!intl::InitializeObject(cx, obj, cx->names().InitializePluralRules, locales, options))
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
PluralRules(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return PluralRules(cx, args, args.isConstructing());
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_PluralRules(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 2);
|
||||
return PluralRules(cx, args, true);
|
||||
}
|
||||
|
||||
static void
|
||||
pluralRules_finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
|
||||
// This is-undefined check shouldn't be necessary, but for internal
|
||||
// brokenness in object allocation code. For the moment, hack around it by
|
||||
// explicitly guarding against the possibility of the reserved slot not
|
||||
// containing a private. See bug 949220.
|
||||
const Value& slot = obj->as<NativeObject>().getReservedSlot(UPLURAL_RULES_SLOT);
|
||||
if (!slot.isUndefined()) {
|
||||
if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
|
||||
uplrules_close(pr);
|
||||
}
|
||||
}
|
||||
|
||||
static JSObject*
|
||||
CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
|
||||
{
|
||||
RootedFunction ctor(cx);
|
||||
ctor = global->createConstructor(cx, &PluralRules, cx->names().PluralRules, 0);
|
||||
if (!ctor)
|
||||
return nullptr;
|
||||
|
||||
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &PluralRulesClass));
|
||||
if (!proto)
|
||||
return nullptr;
|
||||
proto->setReservedSlot(UPLURAL_RULES_SLOT, PrivateValue(nullptr));
|
||||
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, proto))
|
||||
return nullptr;
|
||||
|
||||
if (!JS_DefineFunctions(cx, ctor, pluralRules_static_methods))
|
||||
return nullptr;
|
||||
|
||||
if (!JS_DefineFunctions(cx, proto, pluralRules_methods))
|
||||
return nullptr;
|
||||
|
||||
RootedValue options(cx);
|
||||
if (!intl::CreateDefaultOptions(cx, &options))
|
||||
return nullptr;
|
||||
|
||||
if (!intl::InitializeObject(cx, proto, cx->names().InitializePluralRules, UndefinedHandleValue,
|
||||
options))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue ctorValue(cx, ObjectValue(*ctor));
|
||||
if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0))
|
||||
return nullptr;
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 0);
|
||||
|
||||
RootedValue result(cx);
|
||||
// We're going to use ULocale availableLocales as per ICU recommendation:
|
||||
// https://ssl.icu-project.org/trac/ticket/12756
|
||||
if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
|
||||
return false;
|
||||
args.rval().set(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This creates new UNumberFormat with calculated digit formatting
|
||||
* properties for PluralRules.
|
||||
*
|
||||
* This is similar to NewUNumberFormat but doesn't allow for currency or
|
||||
* percent types.
|
||||
*
|
||||
*/
|
||||
static UNumberFormat*
|
||||
NewUNumberFormatForPluralRules(JSContext* cx, HandleObject 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;
|
||||
JSAutoByteString locale(cx, value.toString());
|
||||
if (!locale)
|
||||
return nullptr;
|
||||
|
||||
uint32_t uMinimumIntegerDigits = 1;
|
||||
uint32_t uMinimumFractionDigits = 0;
|
||||
uint32_t uMaximumFractionDigits = 3;
|
||||
int32_t uMinimumSignificantDigits = -1;
|
||||
int32_t uMaximumSignificantDigits = -1;
|
||||
|
||||
RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
|
||||
bool hasP;
|
||||
if (!HasProperty(cx, internals, id, &hasP))
|
||||
return nullptr;
|
||||
if (hasP) {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMinimumSignificantDigits = value.toInt32();
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMaximumSignificantDigits = value.toInt32();
|
||||
} else {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
}
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UNumberFormat* nf = unum_open(UNUM_DECIMAL, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
|
||||
|
||||
if (uMinimumSignificantDigits != -1) {
|
||||
unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
|
||||
unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
|
||||
unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
|
||||
} else {
|
||||
unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
|
||||
unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
|
||||
unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
|
||||
}
|
||||
|
||||
return toClose.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
RootedObject pluralRules(cx, &args[0].toObject());
|
||||
|
||||
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
|
||||
if (!nf)
|
||||
return false;
|
||||
|
||||
ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
|
||||
|
||||
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
|
||||
if (!internals)
|
||||
return false;
|
||||
|
||||
RootedValue value(cx);
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
|
||||
return false;
|
||||
JSAutoByteString locale(cx, value.toString());
|
||||
if (!locale)
|
||||
return false;
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().type, &value))
|
||||
return false;
|
||||
JSAutoByteString type(cx, value.toString());
|
||||
if (!type)
|
||||
return false;
|
||||
|
||||
double x = args[1].toNumber();
|
||||
|
||||
// We need a NumberFormat in order to format the number
|
||||
// using the number formatting options (minimum/maximum*Digits)
|
||||
// before we push the result to PluralRules
|
||||
//
|
||||
// This should be fixed in ICU 59 and we'll be able to switch to that
|
||||
// API: http://bugs.icu-project.org/trac/ticket/12763
|
||||
//
|
||||
RootedValue fmtNumValue(cx);
|
||||
if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
|
||||
return false;
|
||||
RootedString fmtNumValueString(cx, fmtNumValue.toString());
|
||||
AutoStableStringChars stableChars(cx);
|
||||
if (!stableChars.initTwoByte(cx, fmtNumValueString))
|
||||
return false;
|
||||
|
||||
const UChar* uFmtNumValue = Char16ToUChar(stableChars.twoByteRange().begin().get());
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
UFormattable* fmt = unum_parseToUFormattable(nf, nullptr, uFmtNumValue,
|
||||
stableChars.twoByteRange().length(), 0, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedICUObject<UFormattable, ufmt_close> closeUFormattable(fmt);
|
||||
|
||||
double y = ufmt_getDouble(fmt, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
UPluralType category;
|
||||
|
||||
if (StringsAreEqual(type, "cardinal")) {
|
||||
category = UPLURAL_TYPE_CARDINAL;
|
||||
} else {
|
||||
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
|
||||
category = UPLURAL_TYPE_ORDINAL;
|
||||
}
|
||||
|
||||
UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
|
||||
|
||||
JSString* str = CallICU(cx, [pr, y](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return uplrules_select(pr, y, 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() == 2);
|
||||
|
||||
JSAutoByteString locale(cx, args[0].toString());
|
||||
if (!locale)
|
||||
return false;
|
||||
|
||||
JSAutoByteString type(cx, args[1].toString());
|
||||
if (!type)
|
||||
return false;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
UPluralType category;
|
||||
|
||||
if (StringsAreEqual(type, "cardinal")) {
|
||||
category = UPLURAL_TYPE_CARDINAL;
|
||||
} else {
|
||||
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
|
||||
category = UPLURAL_TYPE_ORDINAL;
|
||||
}
|
||||
|
||||
UPluralRules* pr = uplrules_openForType(
|
||||
IcuLocale(locale.ptr()),
|
||||
category,
|
||||
&status
|
||||
);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
|
||||
|
||||
// We should get a C API for that in ICU 59 and switch to it
|
||||
// https://ssl.icu-project.org/trac/ticket/12772
|
||||
//
|
||||
icu::StringEnumeration* kwenum =
|
||||
reinterpret_cast<icu::PluralRules*>(pr)->getKeywords(status);
|
||||
UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &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;
|
||||
|
||||
RootedValue element(cx);
|
||||
uint32_t i = 0;
|
||||
int32_t catSize;
|
||||
const char* cat;
|
||||
|
||||
do {
|
||||
cat = uenum_next(ue, &catSize, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cat)
|
||||
break;
|
||||
|
||||
JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
element.setString(str);
|
||||
if (!DefineElement(cx, res, i, element))
|
||||
return false;
|
||||
i++;
|
||||
} while (true);
|
||||
|
||||
args.rval().setObject(*res);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**************** RelativeTimeFormat *****************/
|
||||
|
||||
static void relativeTimeFormat_finalize(FreeOp* fop, JSObject* obj);
|
||||
|
|
|
|||
|
|
@ -35,54 +35,6 @@ InitIntlClass(JSContext* cx, HandleObject obj);
|
|||
* The following functions are for use by self-hosted code.
|
||||
*/
|
||||
|
||||
/******************** PluralRules ********************/
|
||||
|
||||
/**
|
||||
* Returns a new PluralRules instance.
|
||||
* Self-hosted code cannot cache this constructor (as it does for others in
|
||||
* Utilities.js) because it is initialized after self-hosted code is compiled.
|
||||
*
|
||||
* Usage: pluralRules = intl_PluralRules(locales, options)
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_PluralRules(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/**
|
||||
* Returns an object indicating the supported locales for plural rules
|
||||
* by having a true-valued property for each such locale with the
|
||||
* canonicalized language tag as the property name. The object has no
|
||||
* prototype.
|
||||
*
|
||||
* Usage: availableLocales = intl_PluralRules_availableLocales()
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/**
|
||||
* Returns a plural rule for the number x according to the effective
|
||||
* locale and the formatting options of the given PluralRules.
|
||||
*
|
||||
* A plural rule is a grammatical category that expresses count distinctions
|
||||
* (such as "one", "two", "few" etc.).
|
||||
*
|
||||
* Usage: rule = intl_SelectPluralRule(pluralRules, x)
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/**
|
||||
* Returns an array of plural rules categories for a given
|
||||
* locale and type.
|
||||
*
|
||||
* Usage: categories = intl_GetPluralCategories(locale, type)
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/******************** RelativeTimeFormat ********************/
|
||||
|
||||
/**
|
||||
|
|
|
|||
473
js/src/builtin/intl/PluralRules.cpp
Normal file
473
js/src/builtin/intl/PluralRules.cpp
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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 "jscntxt.h"
|
||||
|
||||
#include "builtin/intl/CommonFunctions.h"
|
||||
#include "builtin/intl/ICUHeader.h"
|
||||
#include "builtin/intl/ScopedICUObject.h"
|
||||
#include "vm/GlobalObject.h"
|
||||
#include "vm/String.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
using mozilla::AssertedCast;
|
||||
|
||||
using js::intl::CallICU;
|
||||
using js::intl::GetAvailableLocales;
|
||||
using js::intl::IcuLocale;
|
||||
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
|
||||
using js::intl::StringsAreEqual;
|
||||
|
||||
/**************** PluralRules *****************/
|
||||
|
||||
const ClassOps PluralRulesObject::classOps_ = {
|
||||
nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
PluralRulesObject::finalize
|
||||
};
|
||||
|
||||
const Class PluralRulesObject::class_ = {
|
||||
js_Object_str,
|
||||
JSCLASS_HAS_RESERVED_SLOTS(PluralRulesObject::SLOT_COUNT) |
|
||||
JSCLASS_FOREGROUND_FINALIZE,
|
||||
&PluralRulesObject::classOps_
|
||||
};
|
||||
|
||||
#if JS_HAS_TOSOURCE
|
||||
static bool
|
||||
pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
args.rval().setString(cx->names().PluralRules);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
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),
|
||||
#if JS_HAS_TOSOURCE
|
||||
JS_FN(js_toSource_str, pluralRules_toSource, 0, 0),
|
||||
#endif
|
||||
JS_FS_END
|
||||
};
|
||||
|
||||
/**
|
||||
* PluralRules constructor.
|
||||
* Spec: ECMAScript 402 API, PluralRules, 1.1
|
||||
*/
|
||||
static bool
|
||||
PluralRules(JSContext* cx, const CallArgs& args, bool construct)
|
||||
{
|
||||
RootedObject obj(cx);
|
||||
|
||||
if (!construct) {
|
||||
JSObject* intl = GlobalObject::getOrCreateIntlObject(cx, cx->global());
|
||||
if (!intl)
|
||||
return false;
|
||||
RootedValue self(cx, args.thisv());
|
||||
if (!self.isUndefined() && (!self.isObject() || self.toObject() != *intl)) {
|
||||
obj = ToObject(cx, self);
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
bool extensible;
|
||||
if (!IsExtensible(cx, obj, &extensible))
|
||||
return false;
|
||||
if (!extensible)
|
||||
return Throw(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE);
|
||||
} else {
|
||||
construct = true;
|
||||
}
|
||||
}
|
||||
if (construct) {
|
||||
RootedObject proto(cx, GlobalObject::getOrCreatePluralRulesPrototype(cx, cx->global()));
|
||||
if (!proto)
|
||||
return false;
|
||||
obj = NewObjectWithGivenProto<PluralRulesObject>(cx, proto);
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
|
||||
obj->as<NativeObject>().setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
|
||||
}
|
||||
|
||||
RootedValue locales(cx, args.get(0));
|
||||
RootedValue options(cx, args.get(1));
|
||||
|
||||
if (!intl::InitializeObject(cx, obj, cx->names().InitializePluralRules, locales, options))
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
PluralRules(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
return PluralRules(cx, args, args.isConstructing());
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_PluralRules(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 2);
|
||||
return PluralRules(cx, args, true);
|
||||
}
|
||||
|
||||
void
|
||||
js::PluralRulesObject::finalize(FreeOp* fop, JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(fop->onMainThread());
|
||||
|
||||
// This is-undefined check shouldn't be necessary, but for internal
|
||||
// brokenness in object allocation code. For the moment, hack around it by
|
||||
// explicitly guarding against the possibility of the reserved slot not
|
||||
// containing a private. See bug 949220.
|
||||
const Value& slot = obj->as<PluralRulesObject>().getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT);
|
||||
if (!slot.isUndefined()) {
|
||||
if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
|
||||
uplrules_close(pr);
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
|
||||
{
|
||||
RootedFunction ctor(cx);
|
||||
ctor = global->createConstructor(cx, &PluralRules, cx->names().PluralRules, 0);
|
||||
if (!ctor)
|
||||
return nullptr;
|
||||
|
||||
RootedNativeObject proto(cx, GlobalObject::createBlankPrototype(cx, global, &PluralRulesObject::class_));
|
||||
if (!proto)
|
||||
return nullptr;
|
||||
proto->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
|
||||
|
||||
if (!LinkConstructorAndPrototype(cx, ctor, proto))
|
||||
return nullptr;
|
||||
|
||||
if (!JS_DefineFunctions(cx, ctor, pluralRules_static_methods))
|
||||
return nullptr;
|
||||
|
||||
if (!JS_DefineFunctions(cx, proto, pluralRules_methods))
|
||||
return nullptr;
|
||||
|
||||
RootedValue options(cx);
|
||||
if (!intl::CreateDefaultOptions(cx, &options))
|
||||
return nullptr;
|
||||
|
||||
if (!intl::InitializeObject(cx, proto, cx->names().InitializePluralRules, UndefinedHandleValue,
|
||||
options))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RootedValue ctorValue(cx, ObjectValue(*ctor));
|
||||
if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0))
|
||||
return nullptr;
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
MOZ_ASSERT(args.length() == 0);
|
||||
|
||||
RootedValue result(cx);
|
||||
// We're going to use ULocale availableLocales as per ICU recommendation:
|
||||
// https://ssl.icu-project.org/trac/ticket/12756
|
||||
if (!GetAvailableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
|
||||
return false;
|
||||
args.rval().set(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This creates new UNumberFormat with calculated digit formatting
|
||||
* properties for PluralRules.
|
||||
*
|
||||
* This is similar to NewUNumberFormat but doesn't allow for currency or
|
||||
* percent types.
|
||||
*
|
||||
*/
|
||||
static UNumberFormat*
|
||||
NewUNumberFormatForPluralRules(JSContext* cx, HandleObject 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;
|
||||
JSAutoByteString locale(cx, value.toString());
|
||||
if (!locale)
|
||||
return nullptr;
|
||||
|
||||
uint32_t uMinimumIntegerDigits = 1;
|
||||
uint32_t uMinimumFractionDigits = 0;
|
||||
uint32_t uMaximumFractionDigits = 3;
|
||||
int32_t uMinimumSignificantDigits = -1;
|
||||
int32_t uMaximumSignificantDigits = -1;
|
||||
|
||||
RootedId id(cx, NameToId(cx->names().minimumSignificantDigits));
|
||||
bool hasP;
|
||||
if (!HasProperty(cx, internals, id, &hasP))
|
||||
return nullptr;
|
||||
if (hasP) {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMinimumSignificantDigits = value.toInt32();
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMaximumSignificantDigits = value.toInt32();
|
||||
} else {
|
||||
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
|
||||
&value))
|
||||
return nullptr;
|
||||
uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
|
||||
}
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
UNumberFormat* nf = unum_open(UNUM_DECIMAL, nullptr, 0, IcuLocale(locale.ptr()), nullptr, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return nullptr;
|
||||
}
|
||||
ScopedICUObject<UNumberFormat, unum_close> toClose(nf);
|
||||
|
||||
if (uMinimumSignificantDigits != -1) {
|
||||
unum_setAttribute(nf, UNUM_SIGNIFICANT_DIGITS_USED, true);
|
||||
unum_setAttribute(nf, UNUM_MIN_SIGNIFICANT_DIGITS, uMinimumSignificantDigits);
|
||||
unum_setAttribute(nf, UNUM_MAX_SIGNIFICANT_DIGITS, uMaximumSignificantDigits);
|
||||
} else {
|
||||
unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
|
||||
unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
|
||||
unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
|
||||
}
|
||||
|
||||
return toClose.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
RootedObject pluralRules(cx, &args[0].toObject());
|
||||
|
||||
UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
|
||||
if (!nf)
|
||||
return false;
|
||||
|
||||
ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
|
||||
|
||||
RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
|
||||
if (!internals)
|
||||
return false;
|
||||
|
||||
RootedValue value(cx);
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
|
||||
return false;
|
||||
JSAutoByteString locale(cx, value.toString());
|
||||
if (!locale)
|
||||
return false;
|
||||
|
||||
if (!GetProperty(cx, internals, internals, cx->names().type, &value))
|
||||
return false;
|
||||
JSAutoByteString type(cx, value.toString());
|
||||
if (!type)
|
||||
return false;
|
||||
|
||||
double x = args[1].toNumber();
|
||||
|
||||
// We need a NumberFormat in order to format the number
|
||||
// using the number formatting options (minimum/maximum*Digits)
|
||||
// before we push the result to PluralRules
|
||||
//
|
||||
// This should be fixed in ICU 59 and we'll be able to switch to that
|
||||
// API: http://bugs.icu-project.org/trac/ticket/12763
|
||||
//
|
||||
RootedValue fmtNumValue(cx);
|
||||
if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
|
||||
return false;
|
||||
RootedString fmtNumValueString(cx, fmtNumValue.toString());
|
||||
AutoStableStringChars stableChars(cx);
|
||||
if (!stableChars.initTwoByte(cx, fmtNumValueString))
|
||||
return false;
|
||||
|
||||
const UChar* uFmtNumValue = Char16ToUChar(stableChars.twoByteRange().begin().get());
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
UFormattable* fmt = unum_parseToUFormattable(nf, nullptr, uFmtNumValue,
|
||||
stableChars.twoByteRange().length(), 0, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedICUObject<UFormattable, ufmt_close> closeUFormattable(fmt);
|
||||
|
||||
double y = ufmt_getDouble(fmt, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
UPluralType category;
|
||||
|
||||
if (StringsAreEqual(type, "cardinal")) {
|
||||
category = UPLURAL_TYPE_CARDINAL;
|
||||
} else {
|
||||
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
|
||||
category = UPLURAL_TYPE_ORDINAL;
|
||||
}
|
||||
|
||||
UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
|
||||
|
||||
JSString* str = CallICU(cx, [pr, y](UChar* chars, int32_t size, UErrorCode* status) {
|
||||
return uplrules_select(pr, y, 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() == 2);
|
||||
|
||||
JSAutoByteString locale(cx, args[0].toString());
|
||||
if (!locale)
|
||||
return false;
|
||||
|
||||
JSAutoByteString type(cx, args[1].toString());
|
||||
if (!type)
|
||||
return false;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
UPluralType category;
|
||||
|
||||
if (StringsAreEqual(type, "cardinal")) {
|
||||
category = UPLURAL_TYPE_CARDINAL;
|
||||
} else {
|
||||
MOZ_ASSERT(StringsAreEqual(type, "ordinal"));
|
||||
category = UPLURAL_TYPE_ORDINAL;
|
||||
}
|
||||
|
||||
UPluralRules* pr = uplrules_openForType(
|
||||
IcuLocale(locale.ptr()),
|
||||
category,
|
||||
&status
|
||||
);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
|
||||
|
||||
// We should get a C API for that in ICU 59 and switch to it
|
||||
// https://ssl.icu-project.org/trac/ticket/12772
|
||||
//
|
||||
icu::StringEnumeration* kwenum =
|
||||
reinterpret_cast<icu::PluralRules*>(pr)->getKeywords(status);
|
||||
UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &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;
|
||||
|
||||
RootedValue element(cx);
|
||||
uint32_t i = 0;
|
||||
int32_t catSize;
|
||||
const char* cat;
|
||||
|
||||
do {
|
||||
cat = uenum_next(ue, &catSize, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
intl::ReportInternalError(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cat)
|
||||
break;
|
||||
|
||||
JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize);
|
||||
if (!str)
|
||||
return false;
|
||||
|
||||
element.setString(str);
|
||||
if (!DefineElement(cx, res, i, element))
|
||||
return false;
|
||||
i++;
|
||||
} while (true);
|
||||
|
||||
args.rval().setObject(*res);
|
||||
return true;
|
||||
}
|
||||
91
js/src/builtin/intl/PluralRules.h
Normal file
91
js/src/builtin/intl/PluralRules.h
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||||
* vim: set ts=8 sts=4 et sw=4 tw=99:
|
||||
* 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/. */
|
||||
|
||||
#ifndef builtin_intl_PluralRules_h
|
||||
#define builtin_intl_PluralRules_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include "builtin/SelfHostingDefines.h"
|
||||
#include "js/Class.h"
|
||||
#include "js/RootingAPI.h"
|
||||
#include "vm/NativeObject.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
class FreeOp;
|
||||
|
||||
class PluralRulesObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
static const Class class_;
|
||||
|
||||
static constexpr uint32_t INTERNALS_SLOT = 0;
|
||||
static constexpr uint32_t UPLURAL_RULES_SLOT = 1;
|
||||
static constexpr uint32_t SLOT_COUNT = 2;
|
||||
|
||||
static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
|
||||
"INTERNALS_SLOT must match self-hosting define for internals object slot");
|
||||
|
||||
private:
|
||||
static const ClassOps classOps_;
|
||||
|
||||
static void finalize(FreeOp* fop, JSObject* obj);
|
||||
};
|
||||
|
||||
extern JSObject*
|
||||
CreatePluralRulesPrototype(JSContext* cx, JS::Handle<JSObject*> Intl,
|
||||
JS::Handle<GlobalObject*> global);
|
||||
|
||||
/**
|
||||
* Returns a new PluralRules instance.
|
||||
* Self-hosted code cannot cache this constructor (as it does for others in
|
||||
* Utilities.js) because it is initialized after self-hosted code is compiled.
|
||||
*
|
||||
* Usage: pluralRules = intl_PluralRules(locales, options)
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_PluralRules(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/**
|
||||
* Returns an object indicating the supported locales for plural rules
|
||||
* by having a true-valued property for each such locale with the
|
||||
* canonicalized language tag as the property name. The object has no
|
||||
* prototype.
|
||||
*
|
||||
* Usage: availableLocales = intl_PluralRules_availableLocales()
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_PluralRules_availableLocales(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/**
|
||||
* Returns a plural rule for the number x according to the effective
|
||||
* locale and the formatting options of the given PluralRules.
|
||||
*
|
||||
* A plural rule is a grammatical category that expresses count distinctions
|
||||
* (such as "one", "two", "few" etc.).
|
||||
*
|
||||
* Usage: rule = intl_SelectPluralRule(pluralRules, x)
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
/**
|
||||
* Returns an array of plural rules categories for a given
|
||||
* locale and type.
|
||||
*
|
||||
* Usage: categories = intl_GetPluralCategories(locale, type)
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
|
||||
*/
|
||||
extern MOZ_MUST_USE bool
|
||||
intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp);
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* builtin_intl_PluralRules_h */
|
||||
|
|
@ -119,6 +119,7 @@ UNIFIED_SOURCES += [
|
|||
'builtin/intl/CommonFunctions.cpp',
|
||||
'builtin/intl/DateTimeFormat.cpp',
|
||||
'builtin/intl/NumberFormat.cpp',
|
||||
'builtin/intl/PluralRules.cpp',
|
||||
'builtin/intl/SharedIntlData.cpp',
|
||||
'builtin/MapObject.cpp',
|
||||
'builtin/ModuleObject.cpp',
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "builtin/intl/Collator.h"
|
||||
#include "builtin/intl/DateTimeFormat.h"
|
||||
#include "builtin/intl/NumberFormat.h"
|
||||
#include "builtin/intl/PluralRules.h"
|
||||
#include "builtin/MapObject.h"
|
||||
#include "builtin/ModuleObject.h"
|
||||
#include "builtin/Object.h"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue