From ed316832bf89319b51c2cb14a3a35a8142d3ee1a Mon Sep 17 00:00:00 2001 From: Martok Date: Thu, 16 Feb 2023 01:33:43 +0100 Subject: [PATCH] Issue #2046 - Move Intl.PluralRules functionality into builtin/intl/PluralRules.* --- js/src/builtin/Intl.cpp | 444 +------------------------- js/src/builtin/Intl.h | 48 --- js/src/builtin/intl/PluralRules.cpp | 473 ++++++++++++++++++++++++++++ js/src/builtin/intl/PluralRules.h | 91 ++++++ js/src/moz.build | 1 + js/src/vm/SelfHosting.cpp | 1 + 6 files changed, 567 insertions(+), 491 deletions(-) create mode 100644 js/src/builtin/intl/PluralRules.cpp create mode 100644 js/src/builtin/intl/PluralRules.h diff --git a/js/src/builtin/Intl.cpp b/js/src/builtin/Intl.cpp index 3bf86b53a3..70539fadf5 100644 --- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -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().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().getReservedSlot(UPLURAL_RULES_SLOT); - if (!slot.isUndefined()) { - if (UPluralRules* pr = static_cast(slot.toPrivate())) - uplrules_close(pr); - } -} - -static JSObject* -CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle 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(value.toInt32()); - - if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits, - &value)) - return nullptr; - uMinimumFractionDigits = AssertedCast(value.toInt32()); - - if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits, - &value)) - return nullptr; - uMaximumFractionDigits = AssertedCast(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 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 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 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 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 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(pr)->getKeywords(status); - UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &status); - - if (U_FAILURE(status)) { - intl::ReportInternalError(cx); - return false; - } - - ScopedICUObject 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(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); diff --git a/js/src/builtin/Intl.h b/js/src/builtin/Intl.h index 49a76dfb5c..dca971a7be 100644 --- a/js/src/builtin/Intl.h +++ b/js/src/builtin/Intl.h @@ -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 ********************/ /** diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp new file mode 100644 index 0000000000..78a2f3a847 --- /dev/null +++ b/js/src/builtin/intl/PluralRules.cpp @@ -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(cx, proto); + if (!obj) + return false; + + obj->as().setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue()); + obj->as().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().getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT); + if (!slot.isUndefined()) { + if (UPluralRules* pr = static_cast(slot.toPrivate())) + uplrules_close(pr); + } +} + +JSObject* +js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle 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(value.toInt32()); + + if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits, + &value)) + return nullptr; + uMinimumFractionDigits = AssertedCast(value.toInt32()); + + if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits, + &value)) + return nullptr; + uMaximumFractionDigits = AssertedCast(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 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 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 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 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 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(pr)->getKeywords(status); + UEnumeration* ue = uenum_openFromStringEnumeration(kwenum, &status); + + if (U_FAILURE(status)) { + intl::ReportInternalError(cx); + return false; + } + + ScopedICUObject 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(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; +} diff --git a/js/src/builtin/intl/PluralRules.h b/js/src/builtin/intl/PluralRules.h new file mode 100644 index 0000000000..7213786766 --- /dev/null +++ b/js/src/builtin/intl/PluralRules.h @@ -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 Intl, + JS::Handle 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 */ \ No newline at end of file diff --git a/js/src/moz.build b/js/src/moz.build index 54b7423730..23fcd7bdf6 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -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', diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index a8a5e5abff..3248f8d47a 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.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"