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