V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
js-plural-rules.cc
1 // Copyright 2018 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef V8_INTL_SUPPORT
6 #error Internationalization is expected to be enabled.
7 #endif // V8_INTL_SUPPORT
8 
9 #include "src/objects/js-plural-rules.h"
10 
11 #include "src/isolate-inl.h"
12 #include "src/objects/intl-objects.h"
13 #include "src/objects/js-plural-rules-inl.h"
14 #include "unicode/decimfmt.h"
15 #include "unicode/locid.h"
16 #include "unicode/numfmt.h"
17 #include "unicode/plurrule.h"
18 
19 namespace v8 {
20 namespace internal {
21 
22 namespace {
23 
24 bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
25  JSPluralRules::Type type,
26  std::unique_ptr<icu::PluralRules>* pl,
27  std::unique_ptr<icu::DecimalFormat>* nf) {
28  // Make formatter from options. Numbering system is added
29  // to the locale as Unicode extension (if it was specified at all).
30  UErrorCode status = U_ZERO_ERROR;
31 
32  UPluralType icu_type = UPLURAL_TYPE_CARDINAL;
33  if (type == JSPluralRules::Type::ORDINAL) {
34  icu_type = UPLURAL_TYPE_ORDINAL;
35  } else {
36  CHECK_EQ(JSPluralRules::Type::CARDINAL, type);
37  }
38 
39  std::unique_ptr<icu::PluralRules> plural_rules(
40  icu::PluralRules::forLocale(icu_locale, icu_type, status));
41  if (U_FAILURE(status)) {
42  return false;
43  }
44  CHECK_NOT_NULL(plural_rules.get());
45 
46  std::unique_ptr<icu::DecimalFormat> number_format(
47  static_cast<icu::DecimalFormat*>(
48  icu::NumberFormat::createInstance(icu_locale, UNUM_DECIMAL, status)));
49  if (U_FAILURE(status)) {
50  return false;
51  }
52  CHECK_NOT_NULL(number_format.get());
53 
54  *pl = std::move(plural_rules);
55  *nf = std::move(number_format);
56 
57  return true;
58 }
59 
60 void InitializeICUPluralRules(
61  Isolate* isolate, const icu::Locale& icu_locale, JSPluralRules::Type type,
62  std::unique_ptr<icu::PluralRules>* plural_rules,
63  std::unique_ptr<icu::DecimalFormat>* number_format) {
64  bool success = CreateICUPluralRules(isolate, icu_locale, type, plural_rules,
65  number_format);
66  if (!success) {
67  // Remove extensions and try again.
68  icu::Locale no_extension_locale(icu_locale.getBaseName());
69  success = CreateICUPluralRules(isolate, no_extension_locale, type,
70  plural_rules, number_format);
71 
72  if (!success) {
73  FATAL("Failed to create ICU PluralRules, are ICU data files missing?");
74  }
75  }
76 
77  CHECK_NOT_NULL((*plural_rules).get());
78  CHECK_NOT_NULL((*number_format).get());
79 }
80 
81 } // namespace
82 
83 Handle<String> JSPluralRules::TypeAsString() const {
84  switch (type()) {
85  case Type::CARDINAL:
86  return GetReadOnlyRoots().cardinal_string_handle();
87  case Type::ORDINAL:
88  return GetReadOnlyRoots().ordinal_string_handle();
89  case Type::COUNT:
90  UNREACHABLE();
91  }
92 }
93 
94 // static
95 MaybeHandle<JSPluralRules> JSPluralRules::Initialize(
96  Isolate* isolate, Handle<JSPluralRules> plural_rules,
97  Handle<Object> locales, Handle<Object> options_obj) {
98  plural_rules->set_flags(0);
99  // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
100  Maybe<std::vector<std::string>> maybe_requested_locales =
101  Intl::CanonicalizeLocaleList(isolate, locales);
102  MAYBE_RETURN(maybe_requested_locales, Handle<JSPluralRules>());
103  std::vector<std::string> requested_locales =
104  maybe_requested_locales.FromJust();
105 
106  // 2. If options is undefined, then
107  if (options_obj->IsUndefined(isolate)) {
108  // 2. a. Let options be ObjectCreate(null).
109  options_obj = isolate->factory()->NewJSObjectWithNullProto();
110  } else {
111  // 3. Else
112  // 3. a. Let options be ? ToObject(options).
113  ASSIGN_RETURN_ON_EXCEPTION(
114  isolate, options_obj,
115  Object::ToObject(isolate, options_obj, "Intl.PluralRules"),
116  JSPluralRules);
117  }
118 
119  // At this point, options_obj can either be a JSObject or a JSProxy only.
120  Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
121 
122  // 5. Let matcher be ? GetOption(options, "localeMatcher", "string",
123  // « "lookup", "best fit" », "best fit").
124  // 6. Set opt.[[localeMatcher]] to matcher.
125  Maybe<Intl::MatcherOption> maybe_locale_matcher =
126  Intl::GetLocaleMatcher(isolate, options, "Intl.PluralRules");
127  MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSPluralRules>());
128  Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
129 
130  // 7. Let t be ? GetOption(options, "type", "string", « "cardinal",
131  // "ordinal" », "cardinal").
132  Maybe<Type> maybe_type = Intl::GetStringOption<Type>(
133  isolate, options, "type", "Intl.PluralRules", {"cardinal", "ordinal"},
134  {Type::CARDINAL, Type::ORDINAL}, Type::CARDINAL);
135  MAYBE_RETURN(maybe_type, MaybeHandle<JSPluralRules>());
136  Type type = maybe_type.FromJust();
137 
138  // 8. Set pluralRules.[[Type]] to t.
139  plural_rules->set_type(type);
140 
141  // Note: The spec says we should do ResolveLocale after performing
142  // SetNumberFormatDigitOptions but we need the locale to create all
143  // the ICU data structures.
144  //
145  // This isn't observable so we aren't violating the spec.
146 
147  // 11. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]],
148  // requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]],
149  // localeData).
150  Intl::ResolvedLocale r =
151  Intl::ResolveLocale(isolate, JSPluralRules::GetAvailableLocales(),
152  requested_locales, matcher, {});
153 
154  // 18. Set collator.[[Locale]] to r.[[locale]].
155  icu::Locale icu_locale = r.icu_locale;
156  DCHECK(!icu_locale.isBogus());
157 
158  std::map<std::string, std::string> extensions = r.extensions;
159 
160  // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
161  Handle<String> locale_str =
162  isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
163  plural_rules->set_locale(*locale_str);
164 
165  std::unique_ptr<icu::PluralRules> icu_plural_rules;
166  std::unique_ptr<icu::DecimalFormat> icu_decimal_format;
167  InitializeICUPluralRules(isolate, icu_locale, type, &icu_plural_rules,
168  &icu_decimal_format);
169  CHECK_NOT_NULL(icu_plural_rules.get());
170  CHECK_NOT_NULL(icu_decimal_format.get());
171 
172  // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
173  Maybe<bool> done = Intl::SetNumberFormatDigitOptions(
174  isolate, icu_decimal_format.get(), options, 0, 3);
175  MAYBE_RETURN(done, MaybeHandle<JSPluralRules>());
176 
177  Handle<Managed<icu::PluralRules>> managed_plural_rules =
178  Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
179  std::move(icu_plural_rules));
180  plural_rules->set_icu_plural_rules(*managed_plural_rules);
181 
182  Handle<Managed<icu::DecimalFormat>> managed_decimal_format =
183  Managed<icu::DecimalFormat>::FromUniquePtr(isolate, 0,
184  std::move(icu_decimal_format));
185  plural_rules->set_icu_decimal_format(*managed_decimal_format);
186 
187  // 13. Return pluralRules.
188  return plural_rules;
189 }
190 
191 MaybeHandle<String> JSPluralRules::ResolvePlural(
192  Isolate* isolate, Handle<JSPluralRules> plural_rules, double number) {
193  icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
194  CHECK_NOT_NULL(icu_plural_rules);
195 
196  icu::DecimalFormat* icu_decimal_format =
197  plural_rules->icu_decimal_format()->raw();
198  CHECK_NOT_NULL(icu_decimal_format);
199 
200  // Currently, PluralRules doesn't implement all the options for rounding that
201  // the Intl spec provides; format and parse the number to round to the
202  // appropriate amount, then apply PluralRules.
203  //
204  // TODO(littledan): If a future ICU version supports an extended API to avoid
205  // this step, then switch to that API. Bug thread:
206  // http://bugs.icu-project.org/trac/ticket/12763
207  icu::UnicodeString rounded_string;
208  icu_decimal_format->format(number, rounded_string);
209 
210  icu::Formattable formattable;
211  UErrorCode status = U_ZERO_ERROR;
212  icu_decimal_format->parse(rounded_string, formattable, status);
213  CHECK(U_SUCCESS(status));
214 
215  double rounded = formattable.getDouble(status);
216  CHECK(U_SUCCESS(status));
217 
218  icu::UnicodeString result = icu_plural_rules->select(rounded);
219  return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
220  reinterpret_cast<const uint16_t*>(result.getBuffer()), result.length()));
221 }
222 
223 namespace {
224 
225 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
226  Handle<Object> value, const char* key) {
227  Handle<String> key_str = isolate->factory()->NewStringFromAsciiChecked(key);
228 
229  // This is a brand new JSObject that shouldn't already have the same
230  // key so this shouldn't fail.
231  CHECK(JSReceiver::CreateDataProperty(isolate, options, key_str, value,
232  kDontThrow)
233  .FromJust());
234 }
235 
236 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
237  int value, const char* key) {
238  Handle<Smi> value_smi(Smi::FromInt(value), isolate);
239  CreateDataPropertyForOptions(isolate, options, value_smi, key);
240 }
241 
242 } // namespace
243 
244 Handle<JSObject> JSPluralRules::ResolvedOptions(
245  Isolate* isolate, Handle<JSPluralRules> plural_rules) {
246  Handle<JSObject> options =
247  isolate->factory()->NewJSObject(isolate->object_function());
248 
249  Handle<String> locale_value(plural_rules->locale(), isolate);
250  CreateDataPropertyForOptions(isolate, options, locale_value, "locale");
251 
252  CreateDataPropertyForOptions(isolate, options, plural_rules->TypeAsString(),
253  "type");
254 
255  icu::DecimalFormat* icu_decimal_format =
256  plural_rules->icu_decimal_format()->raw();
257  CHECK_NOT_NULL(icu_decimal_format);
258 
259  // This is a safe upcast as icu::DecimalFormat inherits from
260  // icu::NumberFormat.
261  icu::NumberFormat* icu_number_format =
262  static_cast<icu::NumberFormat*>(icu_decimal_format);
263 
264  int min_int_digits = icu_number_format->getMinimumIntegerDigits();
265  CreateDataPropertyForOptions(isolate, options, min_int_digits,
266  "minimumIntegerDigits");
267 
268  int min_fraction_digits = icu_number_format->getMinimumFractionDigits();
269  CreateDataPropertyForOptions(isolate, options, min_fraction_digits,
270  "minimumFractionDigits");
271 
272  int max_fraction_digits = icu_number_format->getMaximumFractionDigits();
273  CreateDataPropertyForOptions(isolate, options, max_fraction_digits,
274  "maximumFractionDigits");
275 
276  if (icu_decimal_format->areSignificantDigitsUsed()) {
277  int min_significant_digits =
278  icu_decimal_format->getMinimumSignificantDigits();
279  CreateDataPropertyForOptions(isolate, options, min_significant_digits,
280  "minimumSignificantDigits");
281 
282  int max_significant_digits =
283  icu_decimal_format->getMaximumSignificantDigits();
284  CreateDataPropertyForOptions(isolate, options, max_significant_digits,
285  "maximumSignificantDigits");
286  }
287 
288  // 6. Let pluralCategories be a List of Strings representing the
289  // possible results of PluralRuleSelect for the selected locale pr.
290  icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
291  CHECK_NOT_NULL(icu_plural_rules);
292 
293  UErrorCode status = U_ZERO_ERROR;
294  std::unique_ptr<icu::StringEnumeration> categories(
295  icu_plural_rules->getKeywords(status));
296  CHECK(U_SUCCESS(status));
297  int32_t count = categories->count(status);
298  CHECK(U_SUCCESS(status));
299 
300  Handle<FixedArray> plural_categories =
301  isolate->factory()->NewFixedArray(count);
302  for (int32_t i = 0; i < count; i++) {
303  const icu::UnicodeString* category = categories->snext(status);
304  CHECK(U_SUCCESS(status));
305  if (category == nullptr) break;
306 
307  std::string keyword;
308  Handle<String> value = isolate->factory()->NewStringFromAsciiChecked(
309  category->toUTF8String(keyword).data());
310  plural_categories->set(i, *value);
311  }
312 
313  // 7. Perform ! CreateDataProperty(options, "pluralCategories",
314  // CreateArrayFromList(pluralCategories)).
315  Handle<JSArray> plural_categories_value =
316  isolate->factory()->NewJSArrayWithElements(plural_categories);
317  CreateDataPropertyForOptions(isolate, options, plural_categories_value,
318  "pluralCategories");
319 
320  return options;
321 }
322 
323 std::set<std::string> JSPluralRules::GetAvailableLocales() {
324  int32_t num_locales = 0;
325  // TODO(ftang): For PluralRules, filter out locales that
326  // don't support PluralRules.
327  // PluralRules is missing an appropriate getAvailableLocales method,
328  // so we should filter from all locales, but it's not clear how; see
329  // https://ssl.icu-project.org/trac/ticket/12756
330  const icu::Locale* icu_available_locales =
331  icu::Locale::getAvailableLocales(num_locales);
332  return Intl::BuildLocaleSet(icu_available_locales, num_locales);
333 }
334 
335 } // namespace internal
336 } // namespace v8
Definition: libplatform.h:13