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