/* -*- 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/WellKnownAtom.h" // js_*_str #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), #ifdef NIGHTLY_BUILD JS_SELF_HOSTED_FN("selectRange", "Intl_PluralRules_selectRange", 2, 0), #endif 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, GenericCreatePrototype, 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 pluralRules(cx); pluralRules = NewObjectWithClassProto(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(); 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 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(value.toInt32()); if (!GetProperty(cx, internals, internals, cx->names().maximumSignificantDigits, &value)) { return nullptr; } uint32_t maximumSignificantDigits = AssertedCast(value.toInt32()); options.mSignificantDigits = mozilla::Some( std::make_pair(minimumSignificantDigits, maximumSignificantDigits)); } else { if (!GetProperty(cx, internals, internals, cx->names().minimumFractionDigits, &value)) { return nullptr; } uint32_t minimumFractionDigits = AssertedCast(value.toInt32()); if (!GetProperty(cx, internals, internals, cx->names().maximumFractionDigits, &value)) { return nullptr; } uint32_t maximumFractionDigits = AssertedCast(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(value.toInt32())); 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 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; } bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 2); Rooted pluralRules( cx, &args[0].toObject().as()); double x = args[1].toNumber(); 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; } /** * ResolvePluralRange ( pluralRules, x, y ) * PluralRuleSelectRange ( locale, type, xp, yp ) */ bool js::intl_SelectPluralRuleRange(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 3); // Steps 1-2. Rooted pluralRules( cx, &args[0].toObject().as()); // 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-10. 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 pluralRules( cx, &args[0].toObject().as()); 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; }