tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

PluralRules.cpp (16086B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* Implementation of the Intl.PluralRules proposal. */
      8 
      9 #include "builtin/intl/PluralRules.h"
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/Casting.h"
     13 #include "mozilla/intl/PluralRules.h"
     14 
     15 #include "builtin/Array.h"
     16 #include "builtin/intl/CommonFunctions.h"
     17 #include "builtin/intl/LocaleNegotiation.h"
     18 #include "gc/GCContext.h"
     19 #include "js/PropertySpec.h"
     20 #include "vm/GlobalObject.h"
     21 #include "vm/JSContext.h"
     22 #include "vm/PlainObject.h"  // js::PlainObject
     23 #include "vm/StringType.h"
     24 
     25 #include "vm/JSObject-inl.h"
     26 #include "vm/NativeObject-inl.h"
     27 
     28 using namespace js;
     29 using namespace js::intl;
     30 
     31 using mozilla::AssertedCast;
     32 
     33 const JSClassOps PluralRulesObject::classOps_ = {
     34    nullptr,                      // addProperty
     35    nullptr,                      // delProperty
     36    nullptr,                      // enumerate
     37    nullptr,                      // newEnumerate
     38    nullptr,                      // resolve
     39    nullptr,                      // mayResolve
     40    PluralRulesObject::finalize,  // finalize
     41    nullptr,                      // call
     42    nullptr,                      // construct
     43    nullptr,                      // trace
     44 };
     45 
     46 const JSClass PluralRulesObject::class_ = {
     47    "Intl.PluralRules",
     48    JSCLASS_HAS_RESERVED_SLOTS(PluralRulesObject::SLOT_COUNT) |
     49        JSCLASS_HAS_CACHED_PROTO(JSProto_PluralRules) |
     50        JSCLASS_FOREGROUND_FINALIZE,
     51    &PluralRulesObject::classOps_,
     52    &PluralRulesObject::classSpec_,
     53 };
     54 
     55 const JSClass& PluralRulesObject::protoClass_ = PlainObject::class_;
     56 
     57 static bool pluralRules_supportedLocalesOf(JSContext* cx, unsigned argc,
     58                                           Value* vp);
     59 
     60 static bool pluralRules_toSource(JSContext* cx, unsigned argc, Value* vp) {
     61  CallArgs args = CallArgsFromVp(argc, vp);
     62  args.rval().setString(cx->names().PluralRules);
     63  return true;
     64 }
     65 
     66 static const JSFunctionSpec pluralRules_static_methods[] = {
     67    JS_FN("supportedLocalesOf", pluralRules_supportedLocalesOf, 1, 0),
     68    JS_FS_END,
     69 };
     70 
     71 static const JSFunctionSpec pluralRules_methods[] = {
     72    JS_SELF_HOSTED_FN("resolvedOptions", "Intl_PluralRules_resolvedOptions", 0,
     73                      0),
     74    JS_SELF_HOSTED_FN("select", "Intl_PluralRules_select", 1, 0),
     75    JS_SELF_HOSTED_FN("selectRange", "Intl_PluralRules_selectRange", 2, 0),
     76    JS_FN("toSource", pluralRules_toSource, 0, 0),
     77    JS_FS_END,
     78 };
     79 
     80 static const JSPropertySpec pluralRules_properties[] = {
     81    JS_STRING_SYM_PS(toStringTag, "Intl.PluralRules", JSPROP_READONLY),
     82    JS_PS_END,
     83 };
     84 
     85 static bool PluralRules(JSContext* cx, unsigned argc, Value* vp);
     86 
     87 const ClassSpec PluralRulesObject::classSpec_ = {
     88    GenericCreateConstructor<PluralRules, 0, gc::AllocKind::FUNCTION>,
     89    GenericCreatePrototype<PluralRulesObject>,
     90    pluralRules_static_methods,
     91    nullptr,
     92    pluralRules_methods,
     93    pluralRules_properties,
     94    nullptr,
     95    ClassSpec::DontDefineConstructor,
     96 };
     97 
     98 /**
     99 * 16.1.1 Intl.PluralRules ( [ locales [ , options ] ] )
    100 *
    101 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
    102 */
    103 static bool PluralRules(JSContext* cx, unsigned argc, Value* vp) {
    104  CallArgs args = CallArgsFromVp(argc, vp);
    105 
    106  // Step 1.
    107  if (!ThrowIfNotConstructing(cx, args, "Intl.PluralRules")) {
    108    return false;
    109  }
    110 
    111  // Step 2 (Inlined 9.1.14, OrdinaryCreateFromConstructor).
    112  RootedObject proto(cx);
    113  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PluralRules,
    114                                          &proto)) {
    115    return false;
    116  }
    117 
    118  Rooted<PluralRulesObject*> pluralRules(cx);
    119  pluralRules = NewObjectWithClassProto<PluralRulesObject>(cx, proto);
    120  if (!pluralRules) {
    121    return false;
    122  }
    123 
    124  HandleValue locales = args.get(0);
    125  HandleValue options = args.get(1);
    126 
    127  // Step 3.
    128  if (!intl::InitializeObject(cx, pluralRules,
    129                              cx->names().InitializePluralRules, locales,
    130                              options)) {
    131    return false;
    132  }
    133 
    134  args.rval().setObject(*pluralRules);
    135  return true;
    136 }
    137 
    138 void js::PluralRulesObject::finalize(JS::GCContext* gcx, JSObject* obj) {
    139  MOZ_ASSERT(gcx->onMainThread());
    140 
    141  auto* pluralRules = &obj->as<PluralRulesObject>();
    142  if (mozilla::intl::PluralRules* pr = pluralRules->getPluralRules()) {
    143    intl::RemoveICUCellMemory(
    144        gcx, obj, PluralRulesObject::UPluralRulesEstimatedMemoryUse);
    145    delete pr;
    146  }
    147 }
    148 
    149 static JSString* KeywordToString(mozilla::intl::PluralRules::Keyword keyword,
    150                                 JSContext* cx) {
    151  using Keyword = mozilla::intl::PluralRules::Keyword;
    152  switch (keyword) {
    153    case Keyword::Zero: {
    154      return cx->names().zero;
    155    }
    156    case Keyword::One: {
    157      return cx->names().one;
    158    }
    159    case Keyword::Two: {
    160      return cx->names().two;
    161    }
    162    case Keyword::Few: {
    163      return cx->names().few;
    164    }
    165    case Keyword::Many: {
    166      return cx->names().many;
    167    }
    168    case Keyword::Other: {
    169      return cx->names().other;
    170    }
    171  }
    172  MOZ_CRASH("Unexpected PluralRules keyword");
    173 }
    174 
    175 /**
    176 * Returns a new intl::PluralRules with the locale and type options of the given
    177 * PluralRules.
    178 */
    179 static mozilla::intl::PluralRules* NewPluralRules(
    180    JSContext* cx, Handle<PluralRulesObject*> pluralRules) {
    181  RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
    182  if (!internals) {
    183    return nullptr;
    184  }
    185 
    186  RootedValue value(cx);
    187 
    188  if (!GetProperty(cx, internals, internals, cx->names().locale, &value)) {
    189    return nullptr;
    190  }
    191  UniqueChars locale = intl::EncodeLocale(cx, value.toString());
    192  if (!locale) {
    193    return nullptr;
    194  }
    195 
    196  using PluralRules = mozilla::intl::PluralRules;
    197  mozilla::intl::PluralRulesOptions options;
    198 
    199  if (!GetProperty(cx, internals, internals, cx->names().type, &value)) {
    200    return nullptr;
    201  }
    202 
    203  {
    204    JSLinearString* type = value.toString()->ensureLinear(cx);
    205    if (!type) {
    206      return nullptr;
    207    }
    208 
    209    if (StringEqualsLiteral(type, "ordinal")) {
    210      options.mPluralType = PluralRules::Type::Ordinal;
    211    } else {
    212      MOZ_ASSERT(StringEqualsLiteral(type, "cardinal"));
    213      options.mPluralType = PluralRules::Type::Cardinal;
    214    }
    215  }
    216 
    217  bool hasMinimumSignificantDigits;
    218  if (!HasProperty(cx, internals, cx->names().minimumSignificantDigits,
    219                   &hasMinimumSignificantDigits)) {
    220    return nullptr;
    221  }
    222 
    223  if (hasMinimumSignificantDigits) {
    224    if (!GetProperty(cx, internals, internals,
    225                     cx->names().minimumSignificantDigits, &value)) {
    226      return nullptr;
    227    }
    228    uint32_t minimumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
    229 
    230    if (!GetProperty(cx, internals, internals,
    231                     cx->names().maximumSignificantDigits, &value)) {
    232      return nullptr;
    233    }
    234    uint32_t maximumSignificantDigits = AssertedCast<uint32_t>(value.toInt32());
    235 
    236    options.mSignificantDigits = mozilla::Some(
    237        std::make_pair(minimumSignificantDigits, maximumSignificantDigits));
    238  }
    239 
    240  bool hasMinimumFractionDigits;
    241  if (!HasProperty(cx, internals, cx->names().minimumFractionDigits,
    242                   &hasMinimumFractionDigits)) {
    243    return nullptr;
    244  }
    245 
    246  if (hasMinimumFractionDigits) {
    247    if (!GetProperty(cx, internals, internals,
    248                     cx->names().minimumFractionDigits, &value)) {
    249      return nullptr;
    250    }
    251    uint32_t minimumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
    252 
    253    if (!GetProperty(cx, internals, internals,
    254                     cx->names().maximumFractionDigits, &value)) {
    255      return nullptr;
    256    }
    257    uint32_t maximumFractionDigits = AssertedCast<uint32_t>(value.toInt32());
    258 
    259    options.mFractionDigits = mozilla::Some(
    260        std::make_pair(minimumFractionDigits, maximumFractionDigits));
    261  }
    262 
    263  if (!GetProperty(cx, internals, internals, cx->names().roundingPriority,
    264                   &value)) {
    265    return nullptr;
    266  }
    267 
    268  {
    269    JSLinearString* roundingPriority = value.toString()->ensureLinear(cx);
    270    if (!roundingPriority) {
    271      return nullptr;
    272    }
    273 
    274    using RoundingPriority =
    275        mozilla::intl::PluralRulesOptions::RoundingPriority;
    276 
    277    RoundingPriority priority;
    278    if (StringEqualsLiteral(roundingPriority, "auto")) {
    279      priority = RoundingPriority::Auto;
    280    } else if (StringEqualsLiteral(roundingPriority, "morePrecision")) {
    281      priority = RoundingPriority::MorePrecision;
    282    } else {
    283      MOZ_ASSERT(StringEqualsLiteral(roundingPriority, "lessPrecision"));
    284      priority = RoundingPriority::LessPrecision;
    285    }
    286 
    287    options.mRoundingPriority = priority;
    288  }
    289 
    290  if (!GetProperty(cx, internals, internals, cx->names().minimumIntegerDigits,
    291                   &value)) {
    292    return nullptr;
    293  }
    294  options.mMinIntegerDigits =
    295      mozilla::Some(AssertedCast<uint32_t>(value.toInt32()));
    296 
    297  if (!GetProperty(cx, internals, internals, cx->names().roundingIncrement,
    298                   &value)) {
    299    return nullptr;
    300  }
    301  options.mRoundingIncrement = AssertedCast<uint32_t>(value.toInt32());
    302 
    303  if (!GetProperty(cx, internals, internals, cx->names().roundingMode,
    304                   &value)) {
    305    return nullptr;
    306  }
    307 
    308  {
    309    JSLinearString* roundingMode = value.toString()->ensureLinear(cx);
    310    if (!roundingMode) {
    311      return nullptr;
    312    }
    313 
    314    using RoundingMode = mozilla::intl::PluralRulesOptions::RoundingMode;
    315 
    316    RoundingMode rounding;
    317    if (StringEqualsLiteral(roundingMode, "halfExpand")) {
    318      // "halfExpand" is the default mode, so we handle it first.
    319      rounding = RoundingMode::HalfExpand;
    320    } else if (StringEqualsLiteral(roundingMode, "ceil")) {
    321      rounding = RoundingMode::Ceil;
    322    } else if (StringEqualsLiteral(roundingMode, "floor")) {
    323      rounding = RoundingMode::Floor;
    324    } else if (StringEqualsLiteral(roundingMode, "expand")) {
    325      rounding = RoundingMode::Expand;
    326    } else if (StringEqualsLiteral(roundingMode, "trunc")) {
    327      rounding = RoundingMode::Trunc;
    328    } else if (StringEqualsLiteral(roundingMode, "halfCeil")) {
    329      rounding = RoundingMode::HalfCeil;
    330    } else if (StringEqualsLiteral(roundingMode, "halfFloor")) {
    331      rounding = RoundingMode::HalfFloor;
    332    } else if (StringEqualsLiteral(roundingMode, "halfTrunc")) {
    333      rounding = RoundingMode::HalfTrunc;
    334    } else {
    335      MOZ_ASSERT(StringEqualsLiteral(roundingMode, "halfEven"));
    336      rounding = RoundingMode::HalfEven;
    337    }
    338 
    339    options.mRoundingMode = rounding;
    340  }
    341 
    342  if (!GetProperty(cx, internals, internals, cx->names().trailingZeroDisplay,
    343                   &value)) {
    344    return nullptr;
    345  }
    346 
    347  {
    348    JSLinearString* trailingZeroDisplay = value.toString()->ensureLinear(cx);
    349    if (!trailingZeroDisplay) {
    350      return nullptr;
    351    }
    352 
    353    if (StringEqualsLiteral(trailingZeroDisplay, "auto")) {
    354      options.mStripTrailingZero = false;
    355    } else {
    356      MOZ_ASSERT(StringEqualsLiteral(trailingZeroDisplay, "stripIfInteger"));
    357      options.mStripTrailingZero = true;
    358    }
    359  }
    360 
    361  auto result = PluralRules::TryCreate(locale.get(), options);
    362  if (result.isErr()) {
    363    intl::ReportInternalError(cx, result.unwrapErr());
    364    return nullptr;
    365  }
    366 
    367  return result.unwrap().release();
    368 }
    369 
    370 static mozilla::intl::PluralRules* GetOrCreatePluralRules(
    371    JSContext* cx, Handle<PluralRulesObject*> pluralRules) {
    372  // Obtain a cached PluralRules object.
    373  mozilla::intl::PluralRules* pr = pluralRules->getPluralRules();
    374  if (pr) {
    375    return pr;
    376  }
    377 
    378  pr = NewPluralRules(cx, pluralRules);
    379  if (!pr) {
    380    return nullptr;
    381  }
    382  pluralRules->setPluralRules(pr);
    383 
    384  intl::AddICUCellMemory(pluralRules,
    385                         PluralRulesObject::UPluralRulesEstimatedMemoryUse);
    386  return pr;
    387 }
    388 
    389 /**
    390 * 16.5.3 ResolvePlural ( pluralRules, n )
    391 * 16.5.2 PluralRuleSelect ( locale, type, n, operands )
    392 *
    393 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
    394 */
    395 bool js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) {
    396  CallArgs args = CallArgsFromVp(argc, vp);
    397  MOZ_ASSERT(args.length() == 2);
    398 
    399  // Steps 1-2.
    400  Rooted<PluralRulesObject*> pluralRules(
    401      cx, &args[0].toObject().as<PluralRulesObject>());
    402 
    403  // Step 3.
    404  double x = args[1].toNumber();
    405 
    406  // Steps 4-11.
    407  using PluralRules = mozilla::intl::PluralRules;
    408  PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
    409  if (!pr) {
    410    return false;
    411  }
    412 
    413  auto keywordResult = pr->Select(x);
    414  if (keywordResult.isErr()) {
    415    intl::ReportInternalError(cx, keywordResult.unwrapErr());
    416    return false;
    417  }
    418 
    419  JSString* str = KeywordToString(keywordResult.unwrap(), cx);
    420  MOZ_ASSERT(str);
    421 
    422  args.rval().setString(str);
    423  return true;
    424 }
    425 
    426 /**
    427 * 16.5.5 ResolvePluralRange ( pluralRules, x, y )
    428 * 16.5.4 PluralRuleSelectRange ( locale, type, xp, yp )
    429 *
    430 * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
    431 */
    432 bool js::intl_SelectPluralRuleRange(JSContext* cx, unsigned argc, Value* vp) {
    433  CallArgs args = CallArgsFromVp(argc, vp);
    434  MOZ_ASSERT(args.length() == 3);
    435 
    436  // Steps 1-2.
    437  Rooted<PluralRulesObject*> pluralRules(
    438      cx, &args[0].toObject().as<PluralRulesObject>());
    439 
    440  // Steps 3-4.
    441  double x = args[1].toNumber();
    442  double y = args[2].toNumber();
    443 
    444  // Step 5.
    445  if (std::isnan(x)) {
    446    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    447                              JSMSG_NAN_NUMBER_RANGE, "start", "PluralRules",
    448                              "selectRange");
    449    return false;
    450  }
    451  if (std::isnan(y)) {
    452    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
    453                              JSMSG_NAN_NUMBER_RANGE, "end", "PluralRules",
    454                              "selectRange");
    455    return false;
    456  }
    457 
    458  using PluralRules = mozilla::intl::PluralRules;
    459  PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
    460  if (!pr) {
    461    return false;
    462  }
    463 
    464  // Steps 6-11.
    465  auto keywordResult = pr->SelectRange(x, y);
    466  if (keywordResult.isErr()) {
    467    intl::ReportInternalError(cx, keywordResult.unwrapErr());
    468    return false;
    469  }
    470 
    471  JSString* str = KeywordToString(keywordResult.unwrap(), cx);
    472  MOZ_ASSERT(str);
    473 
    474  args.rval().setString(str);
    475  return true;
    476 }
    477 
    478 bool js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp) {
    479  CallArgs args = CallArgsFromVp(argc, vp);
    480  MOZ_ASSERT(args.length() == 1);
    481 
    482  Rooted<PluralRulesObject*> pluralRules(
    483      cx, &args[0].toObject().as<PluralRulesObject>());
    484 
    485  using PluralRules = mozilla::intl::PluralRules;
    486  PluralRules* pr = GetOrCreatePluralRules(cx, pluralRules);
    487  if (!pr) {
    488    return false;
    489  }
    490 
    491  auto categoriesResult = pr->Categories();
    492  if (categoriesResult.isErr()) {
    493    intl::ReportInternalError(cx, categoriesResult.unwrapErr());
    494    return false;
    495  }
    496  auto categories = categoriesResult.unwrap();
    497 
    498  ArrayObject* res = NewDenseFullyAllocatedArray(cx, categories.size());
    499  if (!res) {
    500    return false;
    501  }
    502  res->setDenseInitializedLength(categories.size());
    503 
    504  size_t index = 0;
    505  for (auto keyword : {
    506           PluralRules::Keyword::Zero,
    507           PluralRules::Keyword::One,
    508           PluralRules::Keyword::Two,
    509           PluralRules::Keyword::Few,
    510           PluralRules::Keyword::Many,
    511           PluralRules::Keyword::Other,
    512       }) {
    513    if (categories.contains(keyword)) {
    514      JSString* str = KeywordToString(keyword, cx);
    515      MOZ_ASSERT(str);
    516 
    517      res->initDenseElement(index++, StringValue(str));
    518    }
    519  }
    520  MOZ_ASSERT(index == categories.size());
    521 
    522  args.rval().setObject(*res);
    523  return true;
    524 }
    525 
    526 /**
    527 * Intl.PluralRules.supportedLocalesOf ( locales [ , options ] )
    528 */
    529 static bool pluralRules_supportedLocalesOf(JSContext* cx, unsigned argc,
    530                                           Value* vp) {
    531  CallArgs args = CallArgsFromVp(argc, vp);
    532 
    533  // Steps 1-3.
    534  auto* array = SupportedLocalesOf(cx, AvailableLocaleKind::PluralRules,
    535                                   args.get(0), args.get(1));
    536  if (!array) {
    537    return false;
    538  }
    539  args.rval().setObject(*array);
    540  return true;
    541 }