• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/execution/isolate-inl.h"
12 #include "src/objects/intl-objects.h"
13 #include "src/objects/js-number-format.h"
14 #include "src/objects/js-plural-rules-inl.h"
15 #include "unicode/locid.h"
16 #include "unicode/numberformatter.h"
17 #include "unicode/plurrule.h"
18 #include "unicode/unumberformatter.h"
19 
20 namespace v8 {
21 namespace internal {
22 
23 namespace {
24 
CreateICUPluralRules(Isolate * isolate,const icu::Locale & icu_locale,JSPluralRules::Type type,std::unique_ptr<icu::PluralRules> * pl)25 bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
26                           JSPluralRules::Type type,
27                           std::unique_ptr<icu::PluralRules>* pl) {
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     DCHECK_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   DCHECK_NOT_NULL(plural_rules.get());
45 
46   *pl = std::move(plural_rules);
47   return true;
48 }
49 
50 }  // namespace
51 
TypeAsString() const52 Handle<String> JSPluralRules::TypeAsString() const {
53   switch (type()) {
54     case Type::CARDINAL:
55       return GetReadOnlyRoots().cardinal_string_handle();
56     case Type::ORDINAL:
57       return GetReadOnlyRoots().ordinal_string_handle();
58   }
59   UNREACHABLE();
60 }
61 
62 // static
New(Isolate * isolate,Handle<Map> map,Handle<Object> locales,Handle<Object> options_obj)63 MaybeHandle<JSPluralRules> JSPluralRules::New(Isolate* isolate, Handle<Map> map,
64                                               Handle<Object> locales,
65                                               Handle<Object> options_obj) {
66   // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
67   Maybe<std::vector<std::string>> maybe_requested_locales =
68       Intl::CanonicalizeLocaleList(isolate, locales);
69   MAYBE_RETURN(maybe_requested_locales, Handle<JSPluralRules>());
70   std::vector<std::string> requested_locales =
71       maybe_requested_locales.FromJust();
72 
73   // 2. If options is undefined, then
74   if (options_obj->IsUndefined(isolate)) {
75     // 2. a. Let options be ObjectCreate(null).
76     options_obj = isolate->factory()->NewJSObjectWithNullProto();
77   } else {
78     // 3. Else
79     // 3. a. Let options be ? ToObject(options).
80     ASSIGN_RETURN_ON_EXCEPTION(
81         isolate, options_obj,
82         Object::ToObject(isolate, options_obj, "Intl.PluralRules"),
83         JSPluralRules);
84   }
85 
86   // At this point, options_obj can either be a JSObject or a JSProxy only.
87   Handle<JSReceiver> options = Handle<JSReceiver>::cast(options_obj);
88 
89   // 5. Let matcher be ? GetOption(options, "localeMatcher", "string",
90   // « "lookup", "best fit" », "best fit").
91   // 6. Set opt.[[localeMatcher]] to matcher.
92   Maybe<Intl::MatcherOption> maybe_locale_matcher =
93       Intl::GetLocaleMatcher(isolate, options, "Intl.PluralRules");
94   MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSPluralRules>());
95   Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
96 
97   // 7. Let t be ? GetOption(options, "type", "string", « "cardinal",
98   // "ordinal" », "cardinal").
99   Maybe<Type> maybe_type = Intl::GetStringOption<Type>(
100       isolate, options, "type", "Intl.PluralRules", {"cardinal", "ordinal"},
101       {Type::CARDINAL, Type::ORDINAL}, Type::CARDINAL);
102   MAYBE_RETURN(maybe_type, MaybeHandle<JSPluralRules>());
103   Type type = maybe_type.FromJust();
104 
105   // Note: The spec says we should do ResolveLocale after performing
106   // SetNumberFormatDigitOptions but we need the locale to create all
107   // the ICU data structures.
108   //
109   // This isn't observable so we aren't violating the spec.
110 
111   // 11. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]],
112   // requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]],
113   // localeData).
114   Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
115       Intl::ResolveLocale(isolate, JSPluralRules::GetAvailableLocales(),
116                           requested_locales, matcher, {});
117   if (maybe_resolve_locale.IsNothing()) {
118     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
119                     JSPluralRules);
120   }
121   Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
122   Handle<String> locale_str =
123       isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
124 
125   icu::number::LocalizedNumberFormatter icu_number_formatter =
126       icu::number::NumberFormatter::withLocale(r.icu_locale)
127           .roundingMode(UNUM_ROUND_HALFUP);
128 
129   std::unique_ptr<icu::PluralRules> icu_plural_rules;
130   bool success =
131       CreateICUPluralRules(isolate, r.icu_locale, type, &icu_plural_rules);
132   if (!success || icu_plural_rules.get() == nullptr) {
133     // Remove extensions and try again.
134     icu::Locale no_extension_locale(r.icu_locale.getBaseName());
135     success = CreateICUPluralRules(isolate, no_extension_locale, type,
136                                    &icu_plural_rules);
137     icu_number_formatter =
138         icu::number::NumberFormatter::withLocale(no_extension_locale)
139             .roundingMode(UNUM_ROUND_HALFUP);
140 
141     if (!success || icu_plural_rules.get() == nullptr) {
142       THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
143                       JSPluralRules);
144     }
145   }
146 
147   // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
148   Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
149       Intl::SetNumberFormatDigitOptions(isolate, options, 0, 3, false);
150   MAYBE_RETURN(maybe_digit_options, MaybeHandle<JSPluralRules>());
151   Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
152   icu_number_formatter = JSNumberFormat::SetDigitOptionsToFormatter(
153       icu_number_formatter, digit_options);
154 
155   Handle<Managed<icu::PluralRules>> managed_plural_rules =
156       Managed<icu::PluralRules>::FromUniquePtr(isolate, 0,
157                                                std::move(icu_plural_rules));
158 
159   Handle<Managed<icu::number::LocalizedNumberFormatter>>
160       managed_number_formatter =
161           Managed<icu::number::LocalizedNumberFormatter>::FromRawPtr(
162               isolate, 0,
163               new icu::number::LocalizedNumberFormatter(icu_number_formatter));
164 
165   // Now all properties are ready, so we can allocate the result object.
166   Handle<JSPluralRules> plural_rules = Handle<JSPluralRules>::cast(
167       isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
168   DisallowHeapAllocation no_gc;
169   plural_rules->set_flags(0);
170 
171   // 8. Set pluralRules.[[Type]] to t.
172   plural_rules->set_type(type);
173 
174   // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
175   plural_rules->set_locale(*locale_str);
176 
177   plural_rules->set_icu_plural_rules(*managed_plural_rules);
178   plural_rules->set_icu_number_formatter(*managed_number_formatter);
179 
180   // 13. Return pluralRules.
181   return plural_rules;
182 }
183 
ResolvePlural(Isolate * isolate,Handle<JSPluralRules> plural_rules,double number)184 MaybeHandle<String> JSPluralRules::ResolvePlural(
185     Isolate* isolate, Handle<JSPluralRules> plural_rules, double number) {
186   icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules().raw();
187   DCHECK_NOT_NULL(icu_plural_rules);
188 
189   icu::number::LocalizedNumberFormatter* fmt =
190       plural_rules->icu_number_formatter().raw();
191   DCHECK_NOT_NULL(fmt);
192 
193   UErrorCode status = U_ZERO_ERROR;
194   icu::number::FormattedNumber formatted_number =
195       fmt->formatDouble(number, status);
196   DCHECK(U_SUCCESS(status));
197 
198   icu::UnicodeString result =
199       icu_plural_rules->select(formatted_number, status);
200   DCHECK(U_SUCCESS(status));
201 
202   return Intl::ToString(isolate, result);
203 }
204 
205 namespace {
206 
CreateDataPropertyForOptions(Isolate * isolate,Handle<JSObject> options,Handle<Object> value,const char * key)207 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
208                                   Handle<Object> value, const char* key) {
209   Handle<String> key_str = isolate->factory()->NewStringFromAsciiChecked(key);
210 
211   // This is a brand new JSObject that shouldn't already have the same
212   // key so this shouldn't fail.
213   Maybe<bool> maybe = JSReceiver::CreateDataProperty(isolate, options, key_str,
214                                                      value, Just(kDontThrow));
215   DCHECK(maybe.FromJust());
216   USE(maybe);
217 }
218 
CreateDataPropertyForOptions(Isolate * isolate,Handle<JSObject> options,int value,const char * key)219 void CreateDataPropertyForOptions(Isolate* isolate, Handle<JSObject> options,
220                                   int value, const char* key) {
221   Handle<Smi> value_smi(Smi::FromInt(value), isolate);
222   CreateDataPropertyForOptions(isolate, options, value_smi, key);
223 }
224 
225 }  // namespace
226 
ResolvedOptions(Isolate * isolate,Handle<JSPluralRules> plural_rules)227 Handle<JSObject> JSPluralRules::ResolvedOptions(
228     Isolate* isolate, Handle<JSPluralRules> plural_rules) {
229   Handle<JSObject> options =
230       isolate->factory()->NewJSObject(isolate->object_function());
231 
232   Handle<String> locale_value(plural_rules->locale(), isolate);
233   CreateDataPropertyForOptions(isolate, options, locale_value, "locale");
234 
235   CreateDataPropertyForOptions(isolate, options, plural_rules->TypeAsString(),
236                                "type");
237 
238   UErrorCode status = U_ZERO_ERROR;
239   icu::number::LocalizedNumberFormatter* icu_number_formatter =
240       plural_rules->icu_number_formatter().raw();
241   icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
242   DCHECK(U_SUCCESS(status));
243 
244   CreateDataPropertyForOptions(
245       isolate, options,
246       JSNumberFormat::MinimumIntegerDigitsFromSkeleton(skeleton),
247       "minimumIntegerDigits");
248   int32_t min = 0, max = 0;
249 
250   if (JSNumberFormat::SignificantDigitsFromSkeleton(skeleton, &min, &max)) {
251     CreateDataPropertyForOptions(isolate, options, min,
252                                  "minimumSignificantDigits");
253     CreateDataPropertyForOptions(isolate, options, max,
254                                  "maximumSignificantDigits");
255   } else {
256     JSNumberFormat::FractionDigitsFromSkeleton(skeleton, &min, &max);
257     CreateDataPropertyForOptions(isolate, options, min,
258                                  "minimumFractionDigits");
259     CreateDataPropertyForOptions(isolate, options, max,
260                                  "maximumFractionDigits");
261   }
262 
263   // 6. Let pluralCategories be a List of Strings representing the
264   // possible results of PluralRuleSelect for the selected locale pr.
265   icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules().raw();
266   DCHECK_NOT_NULL(icu_plural_rules);
267 
268   std::unique_ptr<icu::StringEnumeration> categories(
269       icu_plural_rules->getKeywords(status));
270   DCHECK(U_SUCCESS(status));
271   int32_t count = categories->count(status);
272   DCHECK(U_SUCCESS(status));
273 
274   Handle<FixedArray> plural_categories =
275       isolate->factory()->NewFixedArray(count);
276   for (int32_t i = 0; i < count; i++) {
277     const icu::UnicodeString* category = categories->snext(status);
278     DCHECK(U_SUCCESS(status));
279     if (category == nullptr) break;
280 
281     std::string keyword;
282     Handle<String> value = isolate->factory()->NewStringFromAsciiChecked(
283         category->toUTF8String(keyword).data());
284     plural_categories->set(i, *value);
285   }
286 
287   // 7. Perform ! CreateDataProperty(options, "pluralCategories",
288   // CreateArrayFromList(pluralCategories)).
289   Handle<JSArray> plural_categories_value =
290       isolate->factory()->NewJSArrayWithElements(plural_categories);
291   CreateDataPropertyForOptions(isolate, options, plural_categories_value,
292                                "pluralCategories");
293 
294   return options;
295 }
296 
297 namespace {
298 
299 class PluralRulesAvailableLocales {
300  public:
PluralRulesAvailableLocales()301   PluralRulesAvailableLocales() {
302     UErrorCode status = U_ZERO_ERROR;
303     std::unique_ptr<icu::StringEnumeration> locales(
304         icu::PluralRules::getAvailableLocales(status));
305     DCHECK(U_SUCCESS(status));
306     int32_t len = 0;
307     const char* locale = nullptr;
308     while ((locale = locales->next(&len, status)) != nullptr &&
309            U_SUCCESS(status)) {
310       std::string str(locale);
311       if (len > 3) {
312         std::replace(str.begin(), str.end(), '_', '-');
313       }
314       set_.insert(std::move(str));
315     }
316   }
Get() const317   const std::set<std::string>& Get() const { return set_; }
318 
319  private:
320   std::set<std::string> set_;
321 };
322 
323 }  // namespace
324 
GetAvailableLocales()325 const std::set<std::string>& JSPluralRules::GetAvailableLocales() {
326   static base::LazyInstance<PluralRulesAvailableLocales>::type
327       available_locales = LAZY_INSTANCE_INITIALIZER;
328   return available_locales.Pointer()->Get();
329 }
330 
331 }  // namespace internal
332 }  // namespace v8
333