summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/intl/PluralRules.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/intl/PluralRules.cpp')
-rw-r--r--js/src/builtin/intl/PluralRules.cpp504
1 files changed, 504 insertions, 0 deletions
diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp
new file mode 100644
index 0000000000..4d7907f2c8
--- /dev/null
+++ b/js/src/builtin/intl/PluralRules.cpp
@@ -0,0 +1,504 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Implementation of the Intl.PluralRules proposal. */
+
+#include "builtin/intl/PluralRules.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/intl/PluralRules.h"
+
+#include "builtin/Array.h"
+#include "builtin/intl/CommonFunctions.h"
+#include "gc/GCContext.h"
+#include "js/PropertySpec.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSContext.h"
+#include "vm/PlainObject.h" // js::PlainObject
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+using mozilla::AssertedCast;
+
+const JSClassOps PluralRulesObject::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ PluralRulesObject::finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+const JSClass PluralRulesObject::class_ = {
+ "Intl.PluralRules",
+ JSCLASS_HAS_RESERVED_SLOTS(PluralRulesObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_PluralRules) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &PluralRulesObject::classOps_, &PluralRulesObject::classSpec_};
+
+const JSClass& PluralRulesObject::protoClass_ = PlainObject::class_;
+
+static bool pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setString(cx->names().PluralRules);
+ return true;
+}
+
+static const JSFunctionSpec pluralRules_static_methods[] = {
+ JS_SELF_HOSTED_FN("supportedLocalesOf",
+ "Intl_PluralRules_supportedLocalesOf", 1, 0),
+ JS_FS_END};
+
+static const JSFunctionSpec pluralRules_methods[] = {
+ JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0,
+ 0),
+ JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
+ JS_SELF_HOSTED_FN("selectRange", "Intl_PluralRules_selectRange", 2, 0),
+ JS_FN("toSource", pluralRules_toSource, 0, 0), JS_FS_END};
+
+static const JSPropertySpec pluralRules_properties[] = {
+ JS_STRING_SYM_PS(toStringTag, "Intl.PluralRules", JSPROP_READONLY),
+ JS_PS_END};
+
+static bool PluralRules(JSContext* cx, unsigned argc, Value* vp);
+
+const ClassSpec PluralRulesObject::classSpec_ = {
+ GenericCreateConstructor<PluralRules, 0, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<PluralRulesObject>,
+ pluralRules_static_methods,
+ nullptr,
+ pluralRules_methods,
+ pluralRules_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor};
+
+/**
+ * 16.1.1 Intl.PluralRules ( [ locales [ , options ] ] )
+ *
+ * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
+ */
+static bool PluralRules(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Intl.PluralRules")) {
+ return false;
+ }
+
+ // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
+ RootedObject proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PluralRules,
+ &proto)) {
+ return false;
+ }
+
+ Rooted<PluralRulesObject*> pluralRules(cx);
+ pluralRules = NewObjectWithClassProto<PluralRulesObject>(cx, proto);
+ if (!pluralRules) {
+ return false;
+ }
+
+ HandleValue locales = args.get(0);
+ HandleValue options = args.get(1);
+
+ // Step 3.
+ if (!intl::InitializeObject(cx, pluralRules,
+ cx->names().InitializePluralRules, locales,
+ options)) {
+ return false;
+ }
+
+ args.rval().setObject(*pluralRules);
+ return true;
+}
+
+void js::PluralRulesObject::finalize(JS::GCContext* gcx, JSObject* obj) {
+ MOZ_ASSERT(gcx->onMainThread());
+
+ auto* pluralRules = &obj->as<PluralRulesObject>();
+ if (mozilla::intl::PluralRules* pr = pluralRules->getPluralRules()) {
+ intl::RemoveICUCellMemory(
+ gcx, obj, PluralRulesObject::UPluralRulesEstimatedMemoryUse);
+ delete pr;
+ }
+}
+
+static JSString* KeywordToString(mozilla::intl::PluralRules::Keyword keyword,
+ JSContext* cx) {
+ using Keyword = mozilla::intl::PluralRules::Keyword;
+ switch (keyword) {
+ case Keyword::Zero: {
+ return cx->names().zero;
+ }
+ case Keyword::One: {
+ return cx->names().one;
+ }
+ case Keyword::Two: {
+ return cx->names().two;
+ }
+ case Keyword::Few: {
+ return cx->names().few;
+ }
+ case Keyword::Many: {
+ return cx->names().many;
+ }
+ case Keyword::Other: {
+ return cx->names().other;
+ }
+ }
+ MOZ_CRASH("Unexpected PluralRules keyword");
+}
+
+/**
+ * Returns a new intl::PluralRules with the locale and type options of the given
+ * PluralRules.
+ */
+static mozilla::intl::PluralRules* NewPluralRules(
+ JSContext* cx, Handle<PluralRulesObject*> pluralRules) {
+ RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
+ if (!internals) {
+ return nullptr;
+ }
+
+ RootedValue value(cx);
+
+ if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
+ return nullptr;
+ }
+ UniqueChars locale = intl::EncodeLocale(cx, value.toString());
+ if (!locale) {
+ return nullptr;
+ }
+
+ using PluralRules = mozilla::intl::PluralRules;
+ mozilla::intl::PluralRulesOptions options;
+
+ if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
+ return nullptr;
+ }
+
+ {
+ JSLinearString* type = value.toString()->ensureLinear(cx);
+ if (!type) {
+ return nullptr;
+ }
+
+ if (StringEqualsLiteral(type, "ordinal")) {
+ options.mPluralType = PluralRules::Type::Ordinal;
+ } else {
+ MOZ_ASSERT(StringEqualsLiteral(type, "cardinal"));
+ options.mPluralType = PluralRules::Type::Cardinal;
+ }
+ }
+
+ bool hasMinimumSignificantDigits;
+ if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
+ &hasMinimumSignificantDigits)) {
+ return nullptr;
+ }
+
+ if (hasMinimumSignificantDigits) {
+ if (!GetProperty(cx, internals, internals,
+ cx->names().minimumSignificantDigits, &value)) {
+ return nullptr;
+ }
+ uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
+
+ if (!GetProperty(cx, internals, internals,
+ cx->names().maximumSignificantDigits, &value)) {
+ return nullptr;
+ }
+ uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
+
+ options.mSignificantDigits = mozilla::Some(
+ std::make_pair(minimumSignificantDigits, maximumSignificantDigits));
+ }
+
+ bool hasMinimumFractionDigits;
+ if (!HasProperty(cx, internals, cx->names().minimumFractionDigits,
+ &hasMinimumFractionDigits)) {
+ return nullptr;
+ }
+
+ if (hasMinimumFractionDigits) {
+ if (!GetProperty(cx, internals, internals,
+ cx->names().minimumFractionDigits, &value)) {
+ return nullptr;
+ }
+ uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
+
+ if (!GetProperty(cx, internals, internals,
+ cx->names().maximumFractionDigits, &value)) {
+ return nullptr;
+ }
+ uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
+
+ options.mFractionDigits = mozilla::Some(
+ std::make_pair(minimumFractionDigits, maximumFractionDigits));
+ }
+
+ if (!GetProperty(cx, internals, internals, cx->names().roundingPriority,
+ &value)) {
+ return nullptr;
+ }
+
+ {
+ JSLinearString* roundingPriority = value.toString()->ensureLinear(cx);
+ if (!roundingPriority) {
+ return nullptr;
+ }
+
+ using RoundingPriority =
+ mozilla::intl::PluralRulesOptions::RoundingPriority;
+
+ RoundingPriority priority;
+ if (StringEqualsLiteral(roundingPriority, "auto")) {
+ priority = RoundingPriority::Auto;
+ } else if (StringEqualsLiteral(roundingPriority, "morePrecision")) {
+ priority = RoundingPriority::MorePrecision;
+ } else {
+ MOZ_ASSERT(StringEqualsLiteral(roundingPriority, "lessPrecision"));
+ priority = RoundingPriority::LessPrecision;
+ }
+
+ options.mRoundingPriority = priority;
+ }
+
+ if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
+ &value)) {
+ return nullptr;
+ }
+ options.mMinIntegerDigits =
+ mozilla::Some(AssertedCast<uint32_t>(value.toInt32()));
+
+ if (!GetProperty(cx, internals, internals, cx->names().roundingIncrement,
+ &value)) {
+ return nullptr;
+ }
+ options.mRoundingIncrement = AssertedCast<uint32_t>(value.toInt32());
+
+ if (!GetProperty(cx, internals, internals, cx->names().roundingMode,
+ &value)) {
+ return nullptr;
+ }
+
+ {
+ JSLinearString* roundingMode = value.toString()->ensureLinear(cx);
+ if (!roundingMode) {
+ return nullptr;
+ }
+
+ using RoundingMode = mozilla::intl::PluralRulesOptions::RoundingMode;
+
+ RoundingMode rounding;
+ if (StringEqualsLiteral(roundingMode, "halfExpand")) {
+ // "halfExpand" is the default mode, so we handle it first.
+ rounding = RoundingMode::HalfExpand;
+ } else if (StringEqualsLiteral(roundingMode, "ceil")) {
+ rounding = RoundingMode::Ceil;
+ } else if (StringEqualsLiteral(roundingMode, "floor")) {
+ rounding = RoundingMode::Floor;
+ } else if (StringEqualsLiteral(roundingMode, "expand")) {
+ rounding = RoundingMode::Expand;
+ } else if (StringEqualsLiteral(roundingMode, "trunc")) {
+ rounding = RoundingMode::Trunc;
+ } else if (StringEqualsLiteral(roundingMode, "halfCeil")) {
+ rounding = RoundingMode::HalfCeil;
+ } else if (StringEqualsLiteral(roundingMode, "halfFloor")) {
+ rounding = RoundingMode::HalfFloor;
+ } else if (StringEqualsLiteral(roundingMode, "halfTrunc")) {
+ rounding = RoundingMode::HalfTrunc;
+ } else {
+ MOZ_ASSERT(StringEqualsLiteral(roundingMode, "halfEven"));
+ rounding = RoundingMode::HalfEven;
+ }
+
+ options.mRoundingMode = rounding;
+ }
+
+ if (!GetProperty(cx, internals, internals, cx->names().trailingZeroDisplay,
+ &value)) {
+ return nullptr;
+ }
+
+ {
+ JSLinearString* trailingZeroDisplay = value.toString()->ensureLinear(cx);
+ if (!trailingZeroDisplay) {
+ return nullptr;
+ }
+
+ if (StringEqualsLiteral(trailingZeroDisplay, "auto")) {
+ options.mStripTrailingZero = false;
+ } else {
+ MOZ_ASSERT(StringEqualsLiteral(trailingZeroDisplay, "stripIfInteger"));
+ options.mStripTrailingZero = true;
+ }
+ }
+
+ auto result = PluralRules::TryCreate(locale.get(), options);
+ if (result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return nullptr;
+ }
+
+ return result.unwrap().release();
+}
+
+static mozilla::intl::PluralRules* GetOrCreatePluralRules(
+ JSContext* cx, Handle<PluralRulesObject*> pluralRules) {
+ // Obtain a cached PluralRules object.
+ mozilla::intl::PluralRules* pr = pluralRules->getPluralRules();
+ if (pr) {
+ return pr;
+ }
+
+ pr = NewPluralRules(cx, pluralRules);
+ if (!pr) {
+ return nullptr;
+ }
+ pluralRules->setPluralRules(pr);
+
+ intl::AddICUCellMemory(pluralRules,
+ PluralRulesObject::UPluralRulesEstimatedMemoryUse);
+ return pr;
+}
+
+/**
+ * 16.5.3 ResolvePlural ( pluralRules, n )
+ * 16.5.2 PluralRuleSelect ( locale, type, n, operands )
+ *
+ * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
+ */
+bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 2);
+
+ // Steps 1-2.
+ Rooted<PluralRulesObject*> pluralRules(
+ cx, &args[0].toObject().as<PluralRulesObject>());
+
+ // Step 3.
+ double x = args[1].toNumber();
+
+ // Steps 4-11.
+ using PluralRules = mozilla::intl::PluralRules;
+ PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
+ if (!pr) {
+ return false;
+ }
+
+ auto keywordResult = pr->Select(x);
+ if (keywordResult.isErr()) {
+ intl::ReportInternalError(cx, keywordResult.unwrapErr());
+ return false;
+ }
+
+ JSString* str = KeywordToString(keywordResult.unwrap(), cx);
+ MOZ_ASSERT(str);
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * 16.5.5 ResolvePluralRange ( pluralRules, x, y )
+ * 16.5.4 PluralRuleSelectRange ( locale, type, xp, yp )
+ *
+ * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
+ */
+bool js::intl_SelectPluralRuleRange(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 3);
+
+ // Steps 1-2.
+ Rooted<PluralRulesObject*> pluralRules(
+ cx, &args[0].toObject().as<PluralRulesObject>());
+
+ // Steps 3-4.
+ double x = args[1].toNumber();
+ double y = args[2].toNumber();
+
+ // Step 5.
+ if (std::isnan(x)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NAN_NUMBER_RANGE, "start", "PluralRules",
+ "selectRange");
+ return false;
+ }
+ if (std::isnan(y)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NAN_NUMBER_RANGE, "end", "PluralRules",
+ "selectRange");
+ return false;
+ }
+
+ using PluralRules = mozilla::intl::PluralRules;
+ PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
+ if (!pr) {
+ return false;
+ }
+
+ // Steps 6-11.
+ auto keywordResult = pr->SelectRange(x, y);
+ if (keywordResult.isErr()) {
+ intl::ReportInternalError(cx, keywordResult.unwrapErr());
+ return false;
+ }
+
+ JSString* str = KeywordToString(keywordResult.unwrap(), cx);
+ MOZ_ASSERT(str);
+
+ args.rval().setString(str);
+ return true;
+}
+
+bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+ MOZ_ASSERT(args.length() == 1);
+
+ Rooted<PluralRulesObject*> pluralRules(
+ cx, &args[0].toObject().as<PluralRulesObject>());
+
+ using PluralRules = mozilla::intl::PluralRules;
+ PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
+ if (!pr) {
+ return false;
+ }
+
+ auto categoriesResult = pr->Categories();
+ if (categoriesResult.isErr()) {
+ intl::ReportInternalError(cx, categoriesResult.unwrapErr());
+ return false;
+ }
+ auto categories = categoriesResult.unwrap();
+
+ ArrayObject* res = NewDenseFullyAllocatedArray(cx, categories.size());
+ if (!res) {
+ return false;
+ }
+ res->setDenseInitializedLength(categories.size());
+
+ size_t index = 0;
+ for (PluralRules::Keyword keyword : categories) {
+ JSString* str = KeywordToString(keyword, cx);
+ MOZ_ASSERT(str);
+
+ res->initDenseElement(index++, StringValue(str));
+ }
+ MOZ_ASSERT(index == categories.size());
+
+ args.rval().setObject(*res);
+ return true;
+}