diff options
Diffstat (limited to 'js/src/builtin/intl/PluralRules.cpp')
-rw-r--r-- | js/src/builtin/intl/PluralRules.cpp | 435 |
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; +} |