summaryrefslogtreecommitdiffstats
path: root/js/src
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2020-12-22 20:32:56 +0000
committerMoonchild <moonchild@palemoon.org>2020-12-22 20:32:56 +0000
commit25b6703e956a33e0ecd8bdc18f423e5eb5dd09d4 (patch)
tree7c410dbb5d672661bf0a567736501ec3f5ef5191 /js/src
parent911cbcd1af7d8becf9694e3b51ce773908e1a93d (diff)
downloadUXP-25b6703e956a33e0ecd8bdc18f423e5eb5dd09d4.tar
UXP-25b6703e956a33e0ecd8bdc18f423e5eb5dd09d4.tar.gz
UXP-25b6703e956a33e0ecd8bdc18f423e5eb5dd09d4.tar.lz
UXP-25b6703e956a33e0ecd8bdc18f423e5eb5dd09d4.tar.xz
UXP-25b6703e956a33e0ecd8bdc18f423e5eb5dd09d4.zip
Issue #1701 - Implement Intl.PluralRules API
Diffstat (limited to 'js/src')
-rw-r--r--js/src/builtin/Intl.cpp486
-rw-r--r--js/src/builtin/Intl.h48
-rw-r--r--js/src/builtin/Intl.js351
-rw-r--r--js/src/vm/CommonPropertyNames.h3
-rw-r--r--js/src/vm/GlobalObject.h6
-rw-r--r--js/src/vm/SelfHosting.cpp3
6 files changed, 831 insertions, 66 deletions
diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp
index d231d7bec..622e773e0 100644
--- a/js/src/builtin/Intl.cpp
+++ b/js/src/builtin/Intl.cpp
@@ -10,6 +10,7 @@
#include "builtin/Intl.h"
+#include "mozilla/Casting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/Range.h"
#include "mozilla/ScopeExit.h"
@@ -22,6 +23,7 @@
#include "jsobj.h"
#include "builtin/IntlTimeZoneData.h"
+#include "unicode/plurrule.h"
#include "unicode/ucal.h"
#include "unicode/ucol.h"
#include "unicode/udat.h"
@@ -29,6 +31,7 @@
#include "unicode/uenum.h"
#include "unicode/unum.h"
#include "unicode/unumsys.h"
+#include "unicode/upluralrules.h"
#include "unicode/ustring.h"
#include "vm/DateTime.h"
#include "vm/GlobalObject.h"
@@ -43,6 +46,7 @@
using namespace js;
+using mozilla::AssertedCast;
using mozilla::IsFinite;
using mozilla::IsNegativeZero;
using mozilla::MakeScopeExit;
@@ -963,6 +967,89 @@ js::intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp)
}
/**
+ *
+ * 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, GetInternals(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)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ 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();
+}
+
+
+/**
* Returns a new UNumberFormat with the locale and number formatting options
* of the given NumberFormat.
*/
@@ -1044,35 +1131,25 @@ NewUNumberFormat(JSContext* cx, HandleObject numberFormat)
if (hasP) {
if (!GetProperty(cx, internals, internals, cx->names().minimumSignificantDigits,
&value))
- {
return nullptr;
- }
- uMinimumSignificantDigits = int32_t(value.toNumber());
+ uMinimumSignificantDigits = value.toInt32();
if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits,
&value))
- {
return nullptr;
- }
- uMaximumSignificantDigits = int32_t(value.toNumber());
+ uMaximumSignificantDigits = value.toInt32();
} else {
if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
&value))
- {
return nullptr;
- }
- uMinimumIntegerDigits = int32_t(value.toNumber());
+ uMinimumIntegerDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits,
&value))
- {
return nullptr;
- }
- uMinimumFractionDigits = int32_t(value.toNumber());
+ uMinimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits,
&value))
- {
return nullptr;
- }
- uMaximumFractionDigits = int32_t(value.toNumber());
+ uMaximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
}
if (!GetProperty(cx, internals, internals, cx->names().useGrouping, &value))
@@ -2323,6 +2400,381 @@ js::intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp)
return true;
}
+/**************** 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 (!IntlInitialize(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 (!CreateDefaultOptions(cx, &options))
+ return nullptr;
+
+ if (!IntlInitialize(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 (!intl_availableLocales(cx, uloc_countAvailable, uloc_getAvailable, &result))
+ return false;
+ args.rval().set(result);
+ return true;
+}
+
+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, GetInternals(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)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ ScopedICUObject<UFormattable, ufmt_close> closeUFormattable(fmt);
+
+ double y = ufmt_getDouble(fmt, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ UPluralType category;
+
+ if (equal(type, "cardinal")) {
+ category = UPLURAL_TYPE_CARDINAL;
+ } else {
+ MOZ_ASSERT(equal(type, "ordinal"));
+ category = UPLURAL_TYPE_ORDINAL;
+ }
+
+ UPluralRules* pr = uplrules_openForType(icuLocale(locale.ptr()), category, &status);
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
+
+ Vector<char16_t, INITIAL_CHAR_BUFFER_SIZE> chars(cx);
+ if (!chars.resize(INITIAL_CHAR_BUFFER_SIZE))
+ return false;
+
+ int size = uplrules_select(pr, y, Char16ToUChar(chars.begin()), INITIAL_CHAR_BUFFER_SIZE, &status);
+ if (status == U_BUFFER_OVERFLOW_ERROR) {
+ if (!chars.resize(size))
+ return false;
+ status = U_ZERO_ERROR;
+ uplrules_select(pr, y, Char16ToUChar(chars.begin()), size, &status);
+ }
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ return false;
+ }
+
+ JSString* str = NewStringCopyN<CanGC>(cx, chars.begin(), size);
+ 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 (equal(type, "cardinal")) {
+ category = UPLURAL_TYPE_CARDINAL;
+ } else {
+ MOZ_ASSERT(equal(type, "ordinal"));
+ category = UPLURAL_TYPE_ORDINAL;
+ }
+
+ UPluralRules* pr = uplrules_openForType(
+ icuLocale(locale.ptr()),
+ category,
+ &status
+ );
+ if (U_FAILURE(status)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ 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)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ 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)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR);
+ 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;
+}
+
bool
js::intl_GetCalendarInfo(JSContext* cx, unsigned argc, Value* vp)
{
@@ -2761,6 +3213,9 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
RootedObject numberFormatProto(cx, CreateNumberFormatPrototype(cx, intl, global));
if (!numberFormatProto)
return false;
+ RootedObject pluralRulesProto(cx, CreatePluralRulesPrototype(cx, intl, global));
+ if (!pluralRulesProto)
+ return false;
// The |Intl| object is fully set up now, so define the global property.
RootedValue intlValue(cx, ObjectValue(*intl));
@@ -2782,6 +3237,7 @@ GlobalObject::initIntlObject(JSContext* cx, Handle<GlobalObject*> global)
global->setReservedSlot(COLLATOR_PROTO, ObjectValue(*collatorProto));
global->setReservedSlot(DATE_TIME_FORMAT_PROTO, ObjectValue(*dateTimeFormatProto));
global->setReservedSlot(NUMBER_FORMAT_PROTO, ObjectValue(*numberFormatProto));
+ global->setReservedSlot(PLURAL_RULES_PROTO, ObjectValue(*pluralRulesProto));
// Also cache |Intl| to implement spec language that conditions behavior
// based on values being equal to "the standard built-in |Intl| object".
diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h
index 5384d9be1..fd1fc5da6 100644
--- a/js/src/builtin/Intl.h
+++ b/js/src/builtin/Intl.h
@@ -358,6 +358,54 @@ intl_patternForSkeleton(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_FormatDateTime(JSContext* cx, unsigned argc, Value* vp);
+/******************** 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);
+
/**
* Returns a plain object with calendar information for a single valid locale
* (callers must perform this validation). The object will have these
diff --git a/js/src/builtin/Intl.js b/js/src/builtin/Intl.js
index 6391c3e70..281b0f424 100644
--- a/js/src/builtin/Intl.js
+++ b/js/src/builtin/Intl.js
@@ -21,6 +21,9 @@
intl_availableCalendars: false,
intl_patternForSkeleton: false,
intl_FormatDateTime: false,
+ intl_SelectPluralRule: false,
+ intl_GetPluralCategories: false,
+ intl_GetCalendarInfo: false,
*/
/*
@@ -857,6 +860,7 @@ function BestAvailableLocaleIgnoringDefault(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, false);
}
+var noRelevantExtensionKeys = [];
/**
* Compares a BCP 47 language priority list against the set of locales in
@@ -1270,7 +1274,9 @@ function initializeIntlObject(obj) {
function setLazyData(internals, type, lazyData)
{
assert(internals.type === "partial", "can't set lazy data for anything but a newborn");
- assert(type === "Collator" || type === "DateTimeFormat" || type == "NumberFormat", "bad type");
+ assert(type === "Collator" || type === "DateTimeFormat" ||
+ type == "NumberFormat" || type === "PluralRules",
+ "bad type");
assert(IsObject(lazyData), "non-object lazy data");
// Set in reverse order so that the .type change is a barrier.
@@ -1320,7 +1326,9 @@ function isInitializedIntlObject(obj) {
if (IsObject(internals)) {
assert(callFunction(std_Object_hasOwnProperty, internals, "type"), "missing type");
var type = internals.type;
- assert(type === "partial" || type === "Collator" || type === "DateTimeFormat" || type === "NumberFormat", "unexpected type");
+ assert(type === "partial" || type === "Collator" ||
+ type === "DateTimeFormat" || type === "NumberFormat" || type === "PluralRules",
+ "unexpected type");
assert(callFunction(std_Object_hasOwnProperty, internals, "lazyData"), "missing lazyData");
assert(callFunction(std_Object_hasOwnProperty, internals, "internalProps"), "missing internalProps");
} else {
@@ -1377,6 +1385,8 @@ function getInternals(obj)
internalProps = resolveCollatorInternals(lazyData)
else if (type === "DateTimeFormat")
internalProps = resolveDateTimeFormatInternals(lazyData)
+ else if (type === "PluralRules")
+ internalProps = resolvePluralRulesInternals(lazyData)
else
internalProps = resolveNumberFormatInternals(lazyData);
setInternalProperties(internals, internalProps);
@@ -1776,45 +1786,39 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
// Step 6.
var opt = lazyNumberFormatData.opt;
- // Compute effective locale.
- // Step 9.
var NumberFormat = numberFormatInternalProperties;
- // Step 10.
+ // Step 9.
var localeData = NumberFormat.localeData;
- // Step 11.
+ // Step 10.
var r = ResolveLocale(callFunction(NumberFormat.availableLocales, NumberFormat),
lazyNumberFormatData.requestedLocales,
lazyNumberFormatData.opt,
NumberFormat.relevantExtensionKeys,
localeData);
- // Steps 12-13. (Step 14 is not relevant to our implementation.)
+ // Steps 11-12. (Step 13 is not relevant to our implementation.)
internalProps.locale = r.locale;
internalProps.numberingSystem = r.nu;
// Compute formatting options.
- // Step 16.
+ // Step 15.
var s = lazyNumberFormatData.style;
internalProps.style = s;
- // Steps 20, 22.
+ // Steps 19, 21.
if (s === "currency") {
internalProps.currency = lazyNumberFormatData.currency;
internalProps.currencyDisplay = lazyNumberFormatData.currencyDisplay;
}
- // Step 24.
internalProps.minimumIntegerDigits = lazyNumberFormatData.minimumIntegerDigits;
- // Steps 27.
internalProps.minimumFractionDigits = lazyNumberFormatData.minimumFractionDigits;
- // Step 30.
internalProps.maximumFractionDigits = lazyNumberFormatData.maximumFractionDigits;
- // Step 33.
if ("minimumSignificantDigits" in lazyNumberFormatData) {
// Note: Intl.NumberFormat.prototype.resolvedOptions() exposes the
// actual presence (versus undefined-ness) of these properties.
@@ -1823,10 +1827,10 @@ function resolveNumberFormatInternals(lazyNumberFormatData) {
internalProps.maximumSignificantDigits = lazyNumberFormatData.maximumSignificantDigits;
}
- // Step 35.
+ // Step 27.
internalProps.useGrouping = lazyNumberFormatData.useGrouping;
- // Step 42.
+ // Step 34.
internalProps.boundFormat = undefined;
// The caller is responsible for associating |internalProps| with the right
@@ -1854,6 +1858,41 @@ function getNumberFormatInternals(obj, methodName) {
return internalProps;
}
+/**
+ * Applies digit options used for number formatting onto the intl object.
+ *
+ * Spec: ECMAScript Internationalization API Specification, 11.1.1.
+ */
+function SetNumberFormatDigitOptions(lazyData, options, mnfdDefault) {
+ // We skip Step 1 because we set the properties on a lazyData object.
+
+ // Step 2-3.
+ assert(IsObject(options), "SetNumberFormatDigitOptions");
+ assert(typeof mnfdDefault === "number", "SetNumberFormatDigitOptions");
+
+ // Steps 4-6.
+ const mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
+ const mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
+ const mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20);
+
+ // Steps 7-8.
+ let mnsd = options.minimumSignificantDigits;
+ let mxsd = options.maximumSignificantDigits;
+
+ // Steps 9-11.
+ lazyData.minimumIntegerDigits = mnid;
+ lazyData.minimumFractionDigits = mnfd;
+ lazyData.maximumFractionDigits = mxfd;
+
+ // Step 12.
+ if (mnsd !== undefined || mxsd !== undefined) {
+ mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
+ mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
+ lazyData.minimumSignificantDigits = mnsd;
+ lazyData.maximumSignificantDigits = mxsd;
+ }
+}
+
/**
* Initializes an object as a NumberFormat.
@@ -1903,7 +1942,7 @@ function InitializeNumberFormat(numberFormat, locales, options) {
// }
//
// Note that lazy data is only installed as a final step of initialization,
- // so every Collator lazy data object has *all* these properties, never a
+ // so every NumberFormat lazy data object has *all* these properties, never a
// subset of them.
var lazyNumberFormatData = std_Object_create(null);
@@ -1933,11 +1972,11 @@ function InitializeNumberFormat(numberFormat, locales, options) {
opt.localeMatcher = matcher;
// Compute formatting options.
- // Step 15.
+ // Step 14.
var s = GetOption(options, "style", "string", ["decimal", "percent", "currency"], "decimal");
lazyNumberFormatData.style = s;
- // Steps 17-20.
+ // Steps 16-19.
var c = GetOption(options, "currency", "string", undefined, undefined);
if (c !== undefined && !IsWellFormedCurrencyCode(c))
ThrowRangeError(JSMSG_INVALID_CURRENCY_CODE, c);
@@ -1946,54 +1985,36 @@ function InitializeNumberFormat(numberFormat, locales, options) {
if (c === undefined)
ThrowTypeError(JSMSG_UNDEFINED_CURRENCY);
- // Steps 20.a-c.
+ // Steps 19.a-c.
c = toASCIIUpperCase(c);
lazyNumberFormatData.currency = c;
cDigits = CurrencyDigits(c);
}
- // Step 21.
+ // Step 20.
var cd = GetOption(options, "currencyDisplay", "string", ["code", "symbol", "name"], "symbol");
if (s === "currency")
lazyNumberFormatData.currencyDisplay = cd;
- // Step 23.
- var mnid = GetNumberOption(options, "minimumIntegerDigits", 1, 21, 1);
- lazyNumberFormatData.minimumIntegerDigits = mnid;
-
- // Steps 25-26.
- var mnfdDefault = (s === "currency") ? cDigits : 0;
- var mnfd = GetNumberOption(options, "minimumFractionDigits", 0, 20, mnfdDefault);
- lazyNumberFormatData.minimumFractionDigits = mnfd;
-
- // Steps 28-29.
- var mxfdDefault;
- if (s === "currency")
- mxfdDefault = std_Math_max(mnfd, cDigits);
- else if (s === "percent")
- mxfdDefault = std_Math_max(mnfd, 0);
- else
- mxfdDefault = std_Math_max(mnfd, 3);
- var mxfd = GetNumberOption(options, "maximumFractionDigits", mnfd, 20, mxfdDefault);
- lazyNumberFormatData.maximumFractionDigits = mxfd;
-
- // Steps 31-32.
- var mnsd = options.minimumSignificantDigits;
- var mxsd = options.maximumSignificantDigits;
+ // Steps 22-24.
+ SetNumberFormatDigitOptions(lazyNumberFormatData, options, s === "currency" ? cDigits: 0);
- // Step 33.
- if (mnsd !== undefined || mxsd !== undefined) {
- mnsd = GetNumberOption(options, "minimumSignificantDigits", 1, 21, 1);
- mxsd = GetNumberOption(options, "maximumSignificantDigits", mnsd, 21, 21);
- lazyNumberFormatData.minimumSignificantDigits = mnsd;
- lazyNumberFormatData.maximumSignificantDigits = mxsd;
+ // Step 25.
+ if (lazyNumberFormatData.maximumFractionDigits === undefined) {
+ let mxfdDefault = s === "currency"
+ ? cDigits
+ : s === "percent"
+ ? 0
+ : 3;
+ lazyNumberFormatData.maximumFractionDigits =
+ std_Math_max(lazyNumberFormatData.minimumFractionDigits, mxfdDefault);
}
- // Step 34.
+ // Step 26.
var g = GetOption(options, "useGrouping", "boolean", undefined, true);
lazyNumberFormatData.useGrouping = g;
- // Step 43.
+ // Steps 35-36.
//
// We've done everything that must be done now: mark the lazy data as fully
// computed and install it.
@@ -2990,6 +3011,234 @@ function resolveICUPattern(pattern, result) {
}
}
+/********** Intl.PluralRules **********/
+
+/**
+ * PluralRules internal properties.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.3.3.
+ */
+var pluralRulesInternalProperties = {
+ _availableLocales: null,
+ availableLocales: function()
+ {
+ var locales = this._availableLocales;
+ if (locales)
+ return locales;
+
+ locales = intl_PluralRules_availableLocales();
+ addSpecialMissingLanguageTags(locales);
+ return (this._availableLocales = locales);
+ }
+};
+
+/**
+ * Compute an internal properties object from |lazyPluralRulesData|.
+ */
+function resolvePluralRulesInternals(lazyPluralRulesData) {
+ assert(IsObject(lazyPluralRulesData), "lazy data not an object?");
+
+ var internalProps = std_Object_create(null);
+
+ var requestedLocales = lazyPluralRulesData.requestedLocales;
+
+ var PluralRules = pluralRulesInternalProperties;
+
+ // Step 13.
+ const r = ResolveLocale(callFunction(PluralRules.availableLocales, PluralRules),
+ lazyPluralRulesData.requestedLocales,
+ lazyPluralRulesData.opt,
+ noRelevantExtensionKeys, undefined);
+
+ // Step 14.
+ internalProps.locale = r.locale;
+ internalProps.type = lazyPluralRulesData.type;
+
+ internalProps.pluralCategories = intl_GetPluralCategories(
+ internalProps.locale,
+ internalProps.type);
+
+ internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
+ internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
+ internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
+
+ if ("minimumSignificantDigits" in lazyPluralRulesData) {
+ assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch");
+ internalProps.minimumSignificantDigits = lazyPluralRulesData.minimumSignificantDigits;
+ internalProps.maximumSignificantDigits = lazyPluralRulesData.maximumSignificantDigits;
+ }
+
+ return internalProps;
+}
+
+/**
+ * Returns an object containing the PluralRules internal properties of |obj|,
+ * or throws a TypeError if |obj| isn't PluralRules-initialized.
+ */
+function getPluralRulesInternals(obj, methodName) {
+ var internals = getIntlObjectInternals(obj, "PluralRules", methodName);
+ assert(internals.type === "PluralRules", "bad type escaped getIntlObjectInternals");
+
+ var internalProps = maybeInternalProperties(internals);
+ if (internalProps)
+ return internalProps;
+
+ internalProps = resolvePluralRulesInternals(internals.lazyData);
+ setInternalProperties(internals, internalProps);
+ return internalProps;
+}
+
+/**
+ * Initializes an object as a PluralRules.
+ *
+ * This method is complicated a moderate bit by its implementing initialization
+ * as a *lazy* concept. Everything that must happen now, does -- but we defer
+ * all the work we can until the object is actually used as a PluralRules.
+ * This later work occurs in |resolvePluralRulesInternals|; steps not noted
+ * here occur there.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.1.1.
+ */
+function InitializePluralRules(pluralRules, locales, options) {
+ assert(IsObject(pluralRules), "InitializePluralRules");
+
+ // Step 1.
+ if (isInitializedIntlObject(pluralRules))
+ ThrowTypeError(JSMSG_INTL_OBJECT_REINITED);
+
+ let internals = initializeIntlObject(pluralRules);
+
+ // Lazy PluralRules data has the following structure:
+ //
+ // {
+ // requestedLocales: List of locales,
+ // type: "cardinal" / "ordinal",
+ //
+ // opt: // opt object computer in InitializePluralRules
+ // {
+ // localeMatcher: "lookup" / "best fit",
+ // }
+ //
+ // minimumIntegerDigits: integer ∈ [1, 21],
+ // minimumFractionDigits: integer ∈ [0, 20],
+ // maximumFractionDigits: integer ∈ [0, 20],
+ //
+ // // optional
+ // minimumSignificantDigits: integer ∈ [1, 21],
+ // maximumSignificantDigits: integer ∈ [1, 21],
+ // }
+ //
+ // Note that lazy data is only installed as a final step of initialization,
+ // so every PluralRules lazy data object has *all* these properties, never a
+ // subset of them.
+ const lazyPluralRulesData = std_Object_create(null);
+
+ // Step 3.
+ let requestedLocales = CanonicalizeLocaleList(locales);
+ lazyPluralRulesData.requestedLocales = requestedLocales;
+
+ // Steps 4-5.
+ if (options === undefined)
+ options = {};
+ else
+ options = ToObject(options);
+
+ // Step 6.
+ const type = GetOption(options, "type", "string", ["cardinal", "ordinal"], "cardinal");
+ lazyPluralRulesData.type = type;
+
+ // Step 8.
+ let opt = new Record();
+ lazyPluralRulesData.opt = opt;
+
+ // Steps 9-10.
+ let matcher = GetOption(options, "localeMatcher", "string", ["lookup", "best fit"], "best fit");
+ opt.localeMatcher = matcher;
+
+
+ // Step 11.
+ SetNumberFormatDigitOptions(lazyPluralRulesData, options, 0);
+
+ // Step 12.
+ if (lazyPluralRulesData.maximumFractionDigits === undefined) {
+ lazyPluralRulesData.maximumFractionDigits =
+ std_Math_max(lazyPluralRulesData.minimumFractionDigits, 3);
+ }
+
+ setLazyData(internals, "PluralRules", lazyPluralRulesData)
+}
+
+/**
+ * Returns the subset of the given locale list for which this locale list has a
+ * matching (possibly fallback) locale. Locales appear in the same order in the
+ * returned list as in the input list.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.3.2.
+ */
+function Intl_PluralRules_supportedLocalesOf(locales /*, options*/) {
+ var options = arguments.length > 1 ? arguments[1] : undefined;
+
+ // Step 1.
+ var availableLocales = callFunction(pluralRulesInternalProperties.availableLocales,
+ pluralRulesInternalProperties);
+ // Step 2.
+ let requestedLocales = CanonicalizeLocaleList(locales);
+
+ // Step 3.
+ return SupportedLocales(availableLocales, requestedLocales, options);
+}
+
+/**
+ * Returns a String value representing the plural category matching
+ * the number passed as value according to the
+ * effective locale and the formatting options of this PluralRules.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.4.3.
+ */
+function Intl_PluralRules_select(value) {
+ // Step 1.
+ let pluralRules = this;
+ // Step 2.
+ let internals = getPluralRulesInternals(pluralRules, "select");
+
+ // Steps 3-4.
+ let n = ToNumber(value);
+
+ // Step 5.
+ return intl_SelectPluralRule(pluralRules, n);
+}
+
+/**
+ * Returns the resolved options for a PluralRules object.
+ *
+ * Spec: ECMAScript 402 API, PluralRules, 1.4.4.
+ */
+function Intl_PluralRules_resolvedOptions() {
+ var internals = getPluralRulesInternals(this, "resolvedOptions");
+
+ var result = {
+ locale: internals.locale,
+ type: internals.type,
+ pluralCategories: callFunction(std_Array_slice, internals.pluralCategories, 0),
+ minimumIntegerDigits: internals.minimumIntegerDigits,
+ minimumFractionDigits: internals.minimumFractionDigits,
+ maximumFractionDigits: internals.maximumFractionDigits,
+ };
+
+ var optionalProperties = [
+ "minimumSignificantDigits",
+ "maximumSignificantDigits"
+ ];
+
+ for (var i = 0; i < optionalProperties.length; i++) {
+ var p = optionalProperties[i];
+ if (callFunction(std_Object_hasOwnProperty, internals, p))
+ _DefineDataProperty(result, p, internals[p]);
+ }
+ return result;
+}
+
+
function Intl_getCanonicalLocales(locales) {
let codes = CanonicalizeLocaleList(locales);
let result = [];
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index 789a573dc..a88406bc6 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -178,6 +178,7 @@
macro(InitializeCollator, InitializeCollator, "InitializeCollator") \
macro(InitializeDateTimeFormat, InitializeDateTimeFormat, "InitializeDateTimeFormat") \
macro(InitializeNumberFormat, InitializeNumberFormat, "InitializeNumberFormat") \
+ macro(InitializePluralRules, InitializePluralRules, "InitializePluralRules") \
macro(innermost, innermost, "innermost") \
macro(inNursery, inNursery, "inNursery") \
macro(input, input, "input") \
@@ -270,6 +271,8 @@
macro(parseInt, parseInt, "parseInt") \
macro(pattern, pattern, "pattern") \
macro(pending, pending, "pending") \
+ macro(PluralRules, PluralRules, "PluralRules") \
+ macro(PluralRulesSelect, PluralRulesSelect, "Intl_PluralRules_Select") \
macro(public, public_, "public") \
macro(preventExtensions, preventExtensions, "preventExtensions") \
macro(private, private_, "private") \
diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h
index 011f90aa1..f9c0149f1 100644
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -109,6 +109,7 @@ class GlobalObject : public NativeObject
COLLATOR_PROTO,
NUMBER_FORMAT_PROTO,
DATE_TIME_FORMAT_PROTO,
+ PLURAL_RULES_PROTO,
MODULE_PROTO,
IMPORT_ENTRY_PROTO,
EXPORT_ENTRY_PROTO,
@@ -507,6 +508,11 @@ class GlobalObject : public NativeObject
return getOrCreateObject(cx, global, DATE_TIME_FORMAT_PROTO, initIntlObject);
}
+ static JSObject*
+ getOrCreatePluralRulesPrototype(JSContext* cx, Handle<GlobalObject*> global) {
+ return getOrCreateObject(cx, global, PLURAL_RULES_PROTO, initIntlObject);
+ }
+
static bool ensureModulePrototypesCreated(JSContext *cx, Handle<GlobalObject*> global);
JSObject* maybeGetModulePrototype() {
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index dc1dfb9fa..df326a69e 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2605,6 +2605,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
+ JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
+ JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
+ JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
JS_INLINABLE_FN("IsRegExpObject",
intrinsic_IsInstanceOfBuiltin<RegExpObject>, 1,0,