v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
js-plural-rules.cc
Go to the documentation of this file.
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
10
17#pragma GCC diagnostic push
18#pragma GCC diagnostic ignored "-Wshadow"
19#include "unicode/locid.h"
20#include "unicode/numberformatter.h"
21#include "unicode/numberrangeformatter.h"
22#include "unicode/plurrule.h"
23#include "unicode/unumberformatter.h"
24#pragma GCC diagnostic pop
25
26namespace v8 {
27namespace internal {
28
29namespace {
30
31bool CreateICUPluralRules(Isolate* isolate, const icu::Locale& icu_locale,
33 std::unique_ptr<icu::PluralRules>* pl) {
34 // Make formatter from options. Numbering system is added
35 // to the locale as Unicode extension (if it was specified at all).
36 UErrorCode status = U_ZERO_ERROR;
37
38 UPluralType icu_type = UPLURAL_TYPE_CARDINAL;
39 if (type == JSPluralRules::Type::ORDINAL) {
40 icu_type = UPLURAL_TYPE_ORDINAL;
41 } else {
43 }
44
45 std::unique_ptr<icu::PluralRules> plural_rules(
46 icu::PluralRules::forLocale(icu_locale, icu_type, status));
47 if (U_FAILURE(status)) {
48 return false;
49 }
50 DCHECK_NOT_NULL(plural_rules.get());
51
52 *pl = std::move(plural_rules);
53 return true;
54}
55
56} // namespace
57
59 switch (type()) {
60 case Type::CARDINAL:
61 return isolate->factory()->cardinal_string();
62 case Type::ORDINAL:
63 return isolate->factory()->ordinal_string();
64 }
66}
67
68// static
70 Isolate* isolate, DirectHandle<Map> map, DirectHandle<Object> locales,
71 DirectHandle<Object> options_obj) {
72 // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
73 Maybe<std::vector<std::string>> maybe_requested_locales =
74 Intl::CanonicalizeLocaleList(isolate, locales);
75 MAYBE_RETURN(maybe_requested_locales, DirectHandle<JSPluralRules>());
76 std::vector<std::string> requested_locales =
77 maybe_requested_locales.FromJust();
78
79 // 2. Set options to ? CoerceOptionsToObject(options).
81 const char* service = "Intl.PluralRules";
83 isolate, options, CoerceOptionsToObject(isolate, options_obj, service));
84
85 // 5. Let matcher be ? GetOption(options, "localeMatcher", "string",
86 // « "lookup", "best fit" », "best fit").
87 // 6. Set opt.[[localeMatcher]] to matcher.
88 Maybe<Intl::MatcherOption> maybe_locale_matcher =
89 Intl::GetLocaleMatcher(isolate, options, service);
90 MAYBE_RETURN(maybe_locale_matcher, MaybeDirectHandle<JSPluralRules>());
91 Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
92
93 // 7. Let t be ? GetOption(options, "type", "string", « "cardinal",
94 // "ordinal" », "cardinal").
96 isolate, options, "type", service, {"cardinal", "ordinal"},
99 Type type = maybe_type.FromJust();
100
101 // Note: The spec says we should do ResolveLocale after performing
102 // SetNumberFormatDigitOptions but we need the locale to create all
103 // the ICU data structures.
104 //
105 // This isn't observable so we aren't violating the spec.
106
107 // 11. Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]],
108 // requestedLocales, opt, %PluralRules%.[[RelevantExtensionKeys]],
109 // localeData).
110 Maybe<Intl::ResolvedLocale> maybe_resolve_locale =
112 requested_locales, matcher, {});
113 if (maybe_resolve_locale.IsNothing()) {
114 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
115 }
116 Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
117 DirectHandle<String> locale_str =
118 isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
119
120 icu::Locale icu_locale = r.icu_locale;
121 icu::number::UnlocalizedNumberFormatter settings =
122 icu::number::UnlocalizedNumberFormatter().roundingMode(UNUM_ROUND_HALFUP);
123
124 std::unique_ptr<icu::PluralRules> icu_plural_rules;
125 bool success =
126 CreateICUPluralRules(isolate, r.icu_locale, type, &icu_plural_rules);
127 if (!success || icu_plural_rules == nullptr) {
128 // Remove extensions and try again.
129 icu::Locale no_extension_locale(icu_locale.getBaseName());
130 success = CreateICUPluralRules(isolate, no_extension_locale, type,
131 &icu_plural_rules);
132 icu_locale = no_extension_locale;
133
134 if (!success || icu_plural_rules == nullptr) {
135 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
136 }
137 }
138
139 // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3).
140 Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
141 Intl::SetNumberFormatDigitOptions(isolate, options, 0, 3, false, service);
142 MAYBE_RETURN(maybe_digit_options, MaybeDirectHandle<JSPluralRules>());
143 Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
144 settings =
145 JSNumberFormat::SetDigitOptionsToFormatter(settings, digit_options);
146
147 icu::number::LocalizedNumberFormatter icu_number_formatter =
148 settings.locale(icu_locale);
149
150 DirectHandle<Managed<icu::PluralRules>> managed_plural_rules =
151 Managed<icu::PluralRules>::From(isolate, 0, std::move(icu_plural_rules));
152
154 managed_number_formatter =
156 isolate, 0,
157 std::make_shared<icu::number::LocalizedNumberFormatter>(
159
160 // Now all properties are ready, so we can allocate the result object.
162 isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
164 plural_rules->set_flags(0);
165
166 // 8. Set pluralRules.[[Type]] to t.
167 plural_rules->set_type(type);
168
169 // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
170 plural_rules->set_locale(*locale_str);
171
172 plural_rules->set_icu_plural_rules(*managed_plural_rules);
173 plural_rules->set_icu_number_formatter(*managed_number_formatter);
174
175 // 13. Return pluralRules.
176 return plural_rules;
177}
178
180 Isolate* isolate, DirectHandle<JSPluralRules> plural_rules, double number) {
181 icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
182 DCHECK_NOT_NULL(icu_plural_rules);
183
184 icu::number::LocalizedNumberFormatter* fmt =
185 plural_rules->icu_number_formatter()->raw();
186 DCHECK_NOT_NULL(fmt);
187
188 UErrorCode status = U_ZERO_ERROR;
189 icu::number::FormattedNumber formatted_number =
190 fmt->formatDouble(number, status);
191 DCHECK(U_SUCCESS(status));
192
193 icu::UnicodeString result =
194 icu_plural_rules->select(formatted_number, status);
195 DCHECK(U_SUCCESS(status));
196
197 return Intl::ToString(isolate, result);
198}
199
201 Isolate* isolate, DirectHandle<JSPluralRules> plural_rules, double x,
202 double y) {
203 icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
204 DCHECK_NOT_NULL(icu_plural_rules);
205
208 isolate, plural_rules->locale(),
209 *plural_rules->icu_number_formatter()->raw());
210 MAYBE_RETURN(maybe_range_formatter, MaybeDirectHandle<String>());
211
212 icu::number::LocalizedNumberRangeFormatter nrfmt =
213 maybe_range_formatter.FromJust();
214
215 UErrorCode status = U_ZERO_ERROR;
216 icu::number::FormattedNumberRange formatted = nrfmt.formatFormattableRange(
217 icu::Formattable(x), icu::Formattable(y), status);
218
219 DCHECK(U_SUCCESS(status));
220 icu::UnicodeString result = icu_plural_rules->select(formatted, status);
221 DCHECK(U_SUCCESS(status));
222
223 return Intl::ToString(isolate, result);
224}
225
226namespace {
227
228void CreateDataPropertyForOptions(Isolate* isolate,
230 DirectHandle<Object> value, const char* key) {
231 DirectHandle<String> key_str =
232 isolate->factory()->NewStringFromAsciiChecked(key);
233
234 // This is a brand new JSObject that shouldn't already have the same
235 // key so this shouldn't fail.
236 Maybe<bool> maybe = JSReceiver::CreateDataProperty(isolate, options, key_str,
237 value, Just(kDontThrow));
238 DCHECK(maybe.FromJust());
239 USE(maybe);
240}
241
242void CreateDataPropertyForOptions(Isolate* isolate,
243 DirectHandle<JSObject> options, int value,
244 const char* key) {
245 DirectHandle<Smi> value_smi(Smi::FromInt(value), isolate);
246 CreateDataPropertyForOptions(isolate, options, value_smi, key);
247}
248
249} // namespace
250
252 Isolate* isolate, DirectHandle<JSPluralRules> plural_rules) {
253 DirectHandle<JSObject> options =
254 isolate->factory()->NewJSObject(isolate->object_function());
255
256 DirectHandle<String> locale_value(plural_rules->locale(), isolate);
257 CreateDataPropertyForOptions(isolate, options, locale_value, "locale");
258
259 CreateDataPropertyForOptions(isolate, options,
260 plural_rules->TypeAsString(isolate), "type");
261
262 UErrorCode status = U_ZERO_ERROR;
263 icu::number::LocalizedNumberFormatter* icu_number_formatter =
264 plural_rules->icu_number_formatter()->raw();
265 icu::UnicodeString skeleton = icu_number_formatter->toSkeleton(status);
266 DCHECK(U_SUCCESS(status));
267
268 CreateDataPropertyForOptions(
269 isolate, options,
271 "minimumIntegerDigits");
272 int32_t min = 0, max = 0;
273
274 if (JSNumberFormat::SignificantDigitsFromSkeleton(skeleton, &min, &max)) {
275 CreateDataPropertyForOptions(isolate, options, min,
276 "minimumSignificantDigits");
277 CreateDataPropertyForOptions(isolate, options, max,
278 "maximumSignificantDigits");
279 } else {
280 JSNumberFormat::FractionDigitsFromSkeleton(skeleton, &min, &max);
281 CreateDataPropertyForOptions(isolate, options, min,
282 "minimumFractionDigits");
283 CreateDataPropertyForOptions(isolate, options, max,
284 "maximumFractionDigits");
285 }
286
287 // 6. Let pluralCategories be a List of Strings containing all possible
288 // results of PluralRuleSelect for the selected locale pr.[[Locale]], sorted
289 // according to the following order: "zero", "one", "two", "few", "many",
290 // "other".
291 icu::PluralRules* icu_plural_rules = plural_rules->icu_plural_rules()->raw();
292 DCHECK_NOT_NULL(icu_plural_rules);
293
294 std::unique_ptr<icu::StringEnumeration> categories(
295 icu_plural_rules->getKeywords(status));
296 DCHECK(U_SUCCESS(status));
297 int32_t count = categories->count(status);
298 DCHECK(U_SUCCESS(status));
299
300 Factory* factory = isolate->factory();
301 DirectHandle<FixedArray> plural_categories = factory->NewFixedArray(count);
302 const std::vector<const char*> kCategories = {"zero", "one", "two",
303 "few", "many", "other"};
304 int32_t index = 0;
305 std::for_each(kCategories.cbegin(), kCategories.cend(), [&](const char* val) {
306 categories->reset(status);
307 DCHECK(U_SUCCESS(status));
308 for (int32_t i = 0; i < count; i++) {
309 int32_t len;
310 const char* cat = categories->next(&len, status);
311 DCHECK(U_SUCCESS(status));
312 if (cat == nullptr) break;
313 if (std::strcmp(val, cat) == 0) {
314 DirectHandle<String> value_string =
315 factory->NewStringFromAsciiChecked(val);
316 plural_categories->set(index++, *value_string);
317 break;
318 }
319 }
320 });
321 DCHECK(count == index);
322
323 // 7. Perform ! CreateDataProperty(options, "pluralCategories",
324 // CreateArrayFromList(pluralCategories)).
325 DirectHandle<JSArray> plural_categories_value =
326 factory->NewJSArrayWithElements(plural_categories);
327 CreateDataPropertyForOptions(isolate, options, plural_categories_value,
328 "pluralCategories");
329
331 isolate, options, factory->roundingIncrement_string(),
332 JSNumberFormat::RoundingIncrement(isolate, skeleton),
334 .FromJust());
336 isolate, options, factory->roundingMode_string(),
337 JSNumberFormat::RoundingModeString(isolate, skeleton),
339 .FromJust());
341 isolate, options, factory->roundingPriority_string(),
344 .FromJust());
346 isolate, options, factory->trailingZeroDisplay_string(),
349 .FromJust());
350
351 return options;
352}
353
354namespace {
355
356class PluralRulesAvailableLocales {
357 public:
358 PluralRulesAvailableLocales() {
359 UErrorCode status = U_ZERO_ERROR;
360 std::unique_ptr<icu::StringEnumeration> locales(
361 icu::PluralRules::getAvailableLocales(status));
362 DCHECK(U_SUCCESS(status));
363 int32_t len = 0;
364 const char* locale = nullptr;
365 while ((locale = locales->next(&len, status)) != nullptr &&
366 U_SUCCESS(status)) {
367 std::string str(locale);
368 if (len > 3) {
369 std::replace(str.begin(), str.end(), '_', '-');
370 }
371 set_.insert(std::move(str));
372 }
373 }
374 const std::set<std::string>& Get() const { return set_; }
375
376 private:
377 std::set<std::string> set_;
378};
379
380} // namespace
381
382const std::set<std::string>& JSPluralRules::GetAvailableLocales() {
384 available_locales = LAZY_INSTANCE_INITIALIZER;
385 return available_locales.Pointer()->Get();
386}
387
388} // namespace internal
389} // namespace v8
V8_INLINE T FromJust() const &
Definition v8-maybe.h:64
V8_INLINE bool IsNothing() const
Definition v8-maybe.h:35
Handle< FixedArray > NewFixedArray(int length, AllocationType allocation=AllocationType::kYoung)
static V8_WARN_UNUSED_RESULT MaybeHandle< String > ToString(Isolate *isolate, const icu::UnicodeString &string)
static Maybe< std::vector< std::string > > CanonicalizeLocaleList(Isolate *isolate, DirectHandle< Object > locales, bool only_return_one_result=false)
static Maybe< ResolvedLocale > ResolveLocale(Isolate *isolate, const std::set< std::string > &available_locales, const std::vector< std::string > &requested_locales, MatcherOption options, const std::set< std::string > &relevant_extension_keys)
static V8_WARN_UNUSED_RESULT Maybe< MatcherOption > GetLocaleMatcher(Isolate *isolate, DirectHandle< JSReceiver > options, const char *method_name)
static V8_WARN_UNUSED_RESULT Maybe< NumberFormatDigitOptions > SetNumberFormatDigitOptions(Isolate *isolate, DirectHandle< JSReceiver > options, int mnfd_default, int mxfd_default, bool notation_is_compact, const char *service)
static int32_t MinimumIntegerDigitsFromSkeleton(const icu::UnicodeString &skeleton)
static DirectHandle< String > TrailingZeroDisplayString(Isolate *isolate, const icu::UnicodeString &skeleton)
static DirectHandle< Object > RoundingIncrement(Isolate *isolate, const icu::UnicodeString &skeleton)
static V8_WARN_UNUSED_RESULT Maybe< icu::number::LocalizedNumberRangeFormatter > GetRangeFormatter(Isolate *isolate, Tagged< String > locale, const icu::number::LocalizedNumberFormatter &number_formatter)
static icu::number::UnlocalizedNumberFormatter SetDigitOptionsToFormatter(const icu::number::UnlocalizedNumberFormatter &settings, const Intl::NumberFormatDigitOptions &digit_options)
static DirectHandle< String > RoundingPriorityString(Isolate *isolate, const icu::UnicodeString &skeleton)
static bool SignificantDigitsFromSkeleton(const icu::UnicodeString &skeleton, int32_t *minimum, int32_t *maximum)
static bool FractionDigitsFromSkeleton(const icu::UnicodeString &skeleton, int32_t *minimum, int32_t *maximum)
static DirectHandle< String > RoundingModeString(Isolate *isolate, const icu::UnicodeString &skeleton)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< String > ResolvePlural(Isolate *isolate, DirectHandle< JSPluralRules > plural_rules, double number)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< String > ResolvePluralRange(Isolate *isolate, DirectHandle< JSPluralRules > plural_rules, double x, double y)
static V8_WARN_UNUSED_RESULT MaybeDirectHandle< JSPluralRules > New(Isolate *isolate, DirectHandle< Map > map, DirectHandle< Object > locales, DirectHandle< Object > options)
Handle< String > TypeAsString(Isolate *isolate) const
static DirectHandle< JSObject > ResolvedOptions(Isolate *isolate, DirectHandle< JSPluralRules > plural_rules)
static V8_EXPORT_PRIVATE const std::set< std::string > & GetAvailableLocales()
static V8_WARN_UNUSED_RESULT Maybe< bool > CreateDataProperty(Isolate *isolate, DirectHandle< JSReceiver > object, DirectHandle< Name > key, DirectHandle< Object > value, Maybe< ShouldThrow > should_throw)
static DirectHandle< Managed< CppType > > From(Isolate *isolate, size_t estimated_size, std::shared_ptr< CppType > shared_ptr, AllocationType allocation_type=AllocationType::kYoung)
Definition managed-inl.h:27
static constexpr Tagged< Smi > FromInt(int value)
Definition smi.h:38
#define ASSIGN_RETURN_ON_EXCEPTION(isolate, dst, call)
Definition isolate.h:291
#define THROW_NEW_ERROR(isolate, call)
Definition isolate.h:307
#define MAYBE_RETURN(call, value)
Definition isolate.h:408
icu::number::FormattedNumber formatted
DirectHandle< JSReceiver > options
ZoneVector< RpoNumber > & result
#define LAZY_INSTANCE_INITIALIZER
int x
ZoneVector< InstructionOperand > * set_
int r
Definition mul-fft.cc:298
V8_INLINE const Operation & Get(const Graph &graph, OpIndex index)
Definition graph.h:1231
MaybeDirectHandle< JSReceiver > CoerceOptionsToObject(Isolate *isolate, DirectHandle< Object > options, const char *method_name)
Maybe< bool > GetStringOption(Isolate *isolate, DirectHandle< JSReceiver > options, const char *property, const std::vector< const char * > &values, const char *method_name, std::unique_ptr< char[]> *result)
Tagged< To > Cast(Tagged< From > value, const v8::SourceLocation &loc=INIT_SOURCE_LOCATION_IN_DEBUG)
Definition casting.h:150
Maybe< T > Just(const T &t)
Definition v8-maybe.h:117
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_NOT_NULL(val)
Definition logging.h:492
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_EQ(v1, v2)
Definition logging.h:485
#define USE(...)
Definition macros.h:293
typename LazyStaticInstance< T, CreateTrait, InitOnceTrait, DestroyTrait >::type type