1 /*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "ecmascript/js_plural_rules.h"
17
18 #include "ecmascript/intl/locale_helper.h"
19 #include "ecmascript/ecma_macros.h"
20 #include "ecmascript/global_env.h"
21 #include "ecmascript/global_env_constants.h"
22 #include "ecmascript/object_factory-inl.h"
23 #include "ecmascript/js_number_format.h"
24
25 namespace panda::ecmascript {
26 constexpr int32_t STRING_SEPARATOR_LENGTH = 4;
27
GetIcuNumberFormatter() const28 icu::number::LocalizedNumberFormatter *JSPluralRules::GetIcuNumberFormatter() const
29 {
30 ASSERT(GetIcuNF().IsJSNativePointer());
31 auto result = JSNativePointer::Cast(GetIcuNF().GetTaggedObject())->GetExternalPointer();
32 return reinterpret_cast<icu::number::LocalizedNumberFormatter *>(result);
33 }
34
FreeIcuNumberFormatter(void * pointer,void * hint)35 void JSPluralRules::FreeIcuNumberFormatter(void *pointer, void* hint)
36 {
37 if (pointer == nullptr) {
38 return;
39 }
40 auto icuNumberFormatter = reinterpret_cast<icu::number::LocalizedNumberFormatter *>(pointer);
41 icuNumberFormatter->~LocalizedNumberFormatter();
42 if (hint != nullptr) {
43 reinterpret_cast<EcmaVM *>(hint)->GetNativeAreaAllocator()->FreeBuffer(pointer);
44 }
45 }
46
SetIcuNumberFormatter(JSThread * thread,const JSHandle<JSPluralRules> & pluralRules,const icu::number::LocalizedNumberFormatter & icuNF,const DeleteEntryPoint & callback)47 void JSPluralRules::SetIcuNumberFormatter(JSThread *thread, const JSHandle<JSPluralRules> &pluralRules,
48 const icu::number::LocalizedNumberFormatter &icuNF, const DeleteEntryPoint &callback)
49 {
50 EcmaVM *ecmaVm = thread->GetEcmaVM();
51 ObjectFactory *factory = ecmaVm->GetFactory();
52
53 icu::number::LocalizedNumberFormatter *icuPointer =
54 ecmaVm->GetNativeAreaAllocator()->New<icu::number::LocalizedNumberFormatter>(icuNF);
55 ASSERT(icuPointer != nullptr);
56 JSTaggedValue data = pluralRules->GetIcuNF();
57 if (data.IsHeapObject() && data.IsJSNativePointer()) {
58 JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
59 native->ResetExternalPointer(icuPointer);
60 return;
61 }
62 JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm);
63 pluralRules->SetIcuNF(thread, pointer.GetTaggedValue());
64 }
65
GetIcuPluralRules() const66 icu::PluralRules *JSPluralRules::GetIcuPluralRules() const
67 {
68 ASSERT(GetIcuPR().IsJSNativePointer());
69 auto result = JSNativePointer::Cast(GetIcuPR().GetTaggedObject())->GetExternalPointer();
70 return reinterpret_cast<icu::PluralRules *>(result);
71 }
72
FreeIcuPluralRules(void * pointer,void * hint)73 void JSPluralRules::FreeIcuPluralRules(void *pointer, void* hint)
74 {
75 if (pointer == nullptr) {
76 return;
77 }
78 auto icuPluralRules = reinterpret_cast<icu::PluralRules *>(pointer);
79 icuPluralRules->~PluralRules();
80 if (hint != nullptr) {
81 reinterpret_cast<EcmaVM *>(hint)->GetNativeAreaAllocator()->FreeBuffer(pointer);
82 }
83 }
84
SetIcuPluralRules(JSThread * thread,const JSHandle<JSPluralRules> & pluralRules,const icu::PluralRules & icuPR,const DeleteEntryPoint & callback)85 void JSPluralRules::SetIcuPluralRules(JSThread *thread, const JSHandle<JSPluralRules> &pluralRules,
86 const icu::PluralRules &icuPR, const DeleteEntryPoint &callback)
87 {
88 [[maybe_unused]] EcmaHandleScope scope(thread);
89 EcmaVM *ecmaVm = thread->GetEcmaVM();
90 ObjectFactory *factory = ecmaVm->GetFactory();
91
92 icu::PluralRules *icuPointer = ecmaVm->GetNativeAreaAllocator()->New<icu::PluralRules>(icuPR);
93 ASSERT(icuPointer != nullptr);
94 JSTaggedValue data = pluralRules->GetIcuPR();
95 if (data.IsHeapObject() && data.IsJSNativePointer()) {
96 JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
97 native->ResetExternalPointer(icuPointer);
98 return;
99 }
100 JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm);
101 pluralRules->SetIcuPR(thread, pointer.GetTaggedValue());
102 }
103
BuildLocaleSet(JSThread * thread,const std::set<std::string> & icuAvailableLocales)104 JSHandle<TaggedArray> JSPluralRules::BuildLocaleSet(JSThread *thread, const std::set<std::string> &icuAvailableLocales)
105 {
106 EcmaVM *ecmaVm = thread->GetEcmaVM();
107 ObjectFactory *factory = ecmaVm->GetFactory();
108 JSHandle<TaggedArray> locales = factory->NewTaggedArray(icuAvailableLocales.size());
109 int32_t index = 0;
110
111 for (const std::string &locale : icuAvailableLocales) {
112 JSHandle<EcmaString> localeStr = factory->NewFromStdString(locale);
113 locales->Set(thread, index++, localeStr);
114 }
115 return locales;
116 }
117
GetNextLocale(icu::StringEnumeration * locales,std::string & localeStr,int32_t * len)118 bool GetNextLocale(icu::StringEnumeration *locales, std::string &localeStr, int32_t *len)
119 {
120 UErrorCode status = U_ZERO_ERROR;
121 const char *locale = nullptr;
122 locale = locales->next(len, status);
123 if (!U_SUCCESS(status) || locale == nullptr) {
124 localeStr = "";
125 return false;
126 }
127 localeStr = std::string(locale);
128 return true;
129 }
130
GetAvailableLocales(JSThread * thread)131 JSHandle<TaggedArray> JSPluralRules::GetAvailableLocales(JSThread *thread)
132 {
133 UErrorCode status = U_ZERO_ERROR;
134 std::unique_ptr<icu::StringEnumeration> locales(icu::PluralRules::getAvailableLocales(status));
135 ASSERT(U_SUCCESS(status));
136 std::set<std::string> set;
137 std::string localeStr;
138 int32_t len = 0;
139 while (GetNextLocale(locales.get(), localeStr, &len)) {
140 if (len >= STRING_SEPARATOR_LENGTH) {
141 std::replace(localeStr.begin(), localeStr.end(), '_', '-');
142 }
143 set.insert(localeStr);
144 }
145 return BuildLocaleSet(thread, set);
146 }
147
148 // InitializePluralRules ( pluralRules, locales, options )
InitializePluralRules(JSThread * thread,const JSHandle<JSPluralRules> & pluralRules,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options)149 JSHandle<JSPluralRules> JSPluralRules::InitializePluralRules(JSThread *thread,
150 const JSHandle<JSPluralRules> &pluralRules,
151 const JSHandle<JSTaggedValue> &locales,
152 const JSHandle<JSTaggedValue> &options)
153 {
154 EcmaVM *ecmaVm = thread->GetEcmaVM();
155 ObjectFactory *factory = ecmaVm->GetFactory();
156 auto globalConst = thread->GlobalConstants();
157
158 // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
159 JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
160 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread);
161
162 // 2&3. If options is undefined, then Let options be ObjectCreate(null). else Let options be ? ToObject(options).
163 JSHandle<JSObject> prOptions;
164 if (!options->IsUndefined()) {
165 prOptions = JSTaggedValue::ToObject(thread, options);
166 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread);
167 } else {
168 prOptions = factory->CreateNullJSObject();
169 }
170
171 // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
172 LocaleMatcherOption matcher =
173 JSLocale::GetOptionOfString(thread, prOptions, globalConst->GetHandledLocaleMatcherString(),
174 {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
175 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
176 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread);
177
178 // 7. Let t be ? GetOption(options, "type", "string", « "cardinal", "ordinal" », "cardinal").
179 JSHandle<JSTaggedValue> property = JSHandle<JSTaggedValue>::Cast(globalConst->GetHandledTypeString());
180 TypeOption type =
181 JSLocale::GetOptionOfString(thread, prOptions, property, { TypeOption::CARDINAL, TypeOption::ORDINAL },
182 { "cardinal", "ordinal" }, TypeOption::CARDINAL);
183 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread);
184
185 // set pluralRules.[[type]] to type
186 pluralRules->SetType(type);
187
188 // Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]], requestedLocales, opt,
189 // %PluralRules%.[[RelevantExtensionKeys]], localeData).
190 JSHandle<TaggedArray> availableLocales;
191 if (requestedLocales->GetLength() == 0) {
192 availableLocales = factory->EmptyArray();
193 } else {
194 availableLocales = GetAvailableLocales(thread);
195 }
196 std::set<std::string> relevantExtensionKeys{""};
197 ResolvedLocale r =
198 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
199 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread);
200 icu::Locale icuLocale = r.localeData;
201
202 // Get ICU numberFormatter with given locale
203 icu::number::LocalizedNumberFormatter icuNumberFormatter =
204 icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP);
205
206 bool success = true;
207 UErrorCode status = U_ZERO_ERROR;
208 UPluralType icuType = UPLURAL_TYPE_CARDINAL;
209 // Trans typeOption to ICU typeOption
210 switch (type) {
211 case TypeOption::ORDINAL:
212 icuType = UPLURAL_TYPE_ORDINAL;
213 break;
214 case TypeOption::CARDINAL:
215 icuType = UPLURAL_TYPE_CARDINAL;
216 break;
217 default:
218 LOG_ECMA(FATAL) << "this branch is unreachable";
219 UNREACHABLE();
220 }
221 std::unique_ptr<icu::PluralRules> icuPluralRules(icu::PluralRules::forLocale(icuLocale, icuType, status));
222 if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion)
223 success = false;
224 }
225
226 // Trans typeOption to ICU typeOption
227 if (!success || icuPluralRules == nullptr) {
228 icu::Locale noExtensionLocale(icuLocale.getBaseName());
229 status = U_ZERO_ERROR;
230 switch (type) {
231 case TypeOption::ORDINAL:
232 icuType = UPLURAL_TYPE_ORDINAL;
233 break;
234 case TypeOption::CARDINAL:
235 icuType = UPLURAL_TYPE_CARDINAL;
236 break;
237 default:
238 LOG_ECMA(FATAL) << "this branch is unreachable";
239 UNREACHABLE();
240 }
241 icuPluralRules.reset(icu::PluralRules::forLocale(icuLocale, icuType, status));
242 }
243 if (U_FAILURE(status) || icuPluralRules == nullptr) { // NOLINT(readability-implicit-bool-conversion)
244 THROW_RANGE_ERROR_AND_RETURN(thread, "cannot create icuPluralRules", pluralRules);
245 }
246
247 // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3, "standard").
248 JSLocale::SetNumberFormatDigitOptions(thread, pluralRules, JSHandle<JSTaggedValue>::Cast(prOptions), MNFD_DEFAULT,
249 MXFD_DEFAULT, NotationOption::STANDARD);
250 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread);
251 icuNumberFormatter = JSNumberFormat::SetICUFormatterDigitOptions(icuNumberFormatter, pluralRules);
252
253 // Set pluralRules.[[IcuPluralRules]] to icuPluralRules
254 SetIcuPluralRules(thread, pluralRules, *icuPluralRules, JSPluralRules::FreeIcuPluralRules);
255
256 // Set pluralRules.[[IcuNumberFormat]] to icuNumberFormatter
257 SetIcuNumberFormatter(thread, pluralRules, icuNumberFormatter, JSPluralRules::FreeIcuNumberFormatter);
258
259 // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]].
260 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
261 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread);
262 pluralRules->SetLocale(thread, localeStr.GetTaggedValue());
263
264 // 13. Return pluralRules.
265 return pluralRules;
266 }
267
FormatNumericToString(JSThread * thread,const icu::number::LocalizedNumberFormatter * icuFormatter,const icu::PluralRules * icuPluralRules,double n)268 JSHandle<EcmaString> FormatNumericToString(JSThread *thread, const icu::number::LocalizedNumberFormatter *icuFormatter,
269 const icu::PluralRules *icuPluralRules, double n)
270 {
271 UErrorCode status = U_ZERO_ERROR;
272 icu::number::FormattedNumber formatted = icuFormatter->formatDouble(n, status);
273 if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion)
274 JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception());
275 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid resolve number", JSHandle<EcmaString>::Cast(exception));
276 }
277
278 icu::UnicodeString uString = icuPluralRules->select(formatted, status);
279 if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion)
280 JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception());
281 THROW_RANGE_ERROR_AND_RETURN(thread, "invalid resolve number", JSHandle<EcmaString>::Cast(exception));
282 }
283
284 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
285 JSHandle<EcmaString> result =
286 factory->NewFromUtf16(reinterpret_cast<const uint16_t *>(uString.getBuffer()), uString.length());
287 return result;
288 }
ResolvePlural(JSThread * thread,const JSHandle<JSPluralRules> & pluralRules,double n)289 JSHandle<EcmaString> JSPluralRules::ResolvePlural(JSThread *thread, const JSHandle<JSPluralRules> &pluralRules,
290 double n)
291 {
292 icu::PluralRules *icuPluralRules = pluralRules->GetIcuPluralRules();
293 icu::number::LocalizedNumberFormatter *icuFormatter = pluralRules->GetIcuNumberFormatter();
294 if (icuPluralRules == nullptr || icuFormatter == nullptr) {
295 return JSHandle<EcmaString>(thread, JSTaggedValue::Undefined());
296 }
297
298 JSHandle<EcmaString> result = FormatNumericToString(thread, icuFormatter, icuPluralRules, n);
299 RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
300 return result;
301 }
302
ResolvedOptions(JSThread * thread,const JSHandle<JSPluralRules> & pluralRules,const JSHandle<JSObject> & options)303 void JSPluralRules::ResolvedOptions(JSThread *thread, const JSHandle<JSPluralRules> &pluralRules,
304 const JSHandle<JSObject> &options)
305 {
306 EcmaVM *ecmaVm = thread->GetEcmaVM();
307 ObjectFactory *factory = ecmaVm->GetFactory();
308 auto globalConst = thread->GlobalConstants();
309
310 // [[Locale]]
311 JSHandle<JSTaggedValue> property = JSHandle<JSTaggedValue>::Cast(globalConst->GetHandledLocaleString());
312 JSHandle<EcmaString> locale(thread, pluralRules->GetLocale());
313 PropertyDescriptor localeDesc(thread, JSHandle<JSTaggedValue>::Cast(locale), true, true, true);
314 JSObject::DefineOwnProperty(thread, options, property, localeDesc);
315
316 // [[type]]
317 property = JSHandle<JSTaggedValue>::Cast(globalConst->GetHandledTypeString());
318 JSHandle<JSTaggedValue> typeValue;
319 if (pluralRules->GetType() == TypeOption::CARDINAL) {
320 typeValue = globalConst->GetHandledCardinalString();
321 } else {
322 typeValue = globalConst->GetHandledOrdinalString();
323 }
324 PropertyDescriptor typeDesc(thread, typeValue, true, true, true);
325 JSObject::DefineOwnProperty(thread, options, property, typeDesc);
326
327 // [[MinimumIntegerDigits]]
328 property = JSHandle<JSTaggedValue>::Cast(globalConst->GetHandledMinimumIntegerDigitsString());
329 JSHandle<JSTaggedValue> minimumIntegerDigits(thread, pluralRules->GetMinimumIntegerDigits());
330 JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumIntegerDigits);
331 RETURN_IF_ABRUPT_COMPLETION(thread);
332
333 RoundingType roundingType = pluralRules->GetRoundingType();
334 if (roundingType == RoundingType::SIGNIFICANTDIGITS) {
335 // [[MinimumSignificantDigits]]
336 property = globalConst->GetHandledMinimumSignificantDigitsString();
337 JSHandle<JSTaggedValue> minimumSignificantDigits(thread, pluralRules->GetMinimumSignificantDigits());
338 JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits);
339 RETURN_IF_ABRUPT_COMPLETION(thread);
340 // [[MaximumSignificantDigits]]
341 property = globalConst->GetHandledMaximumSignificantDigitsString();
342 JSHandle<JSTaggedValue> maximumSignificantDigits(thread, pluralRules->GetMaximumSignificantDigits());
343 JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits);
344 RETURN_IF_ABRUPT_COMPLETION(thread);
345 } else {
346 // [[MinimumFractionDigits]]
347 property = globalConst->GetHandledMinimumFractionDigitsString();
348 JSHandle<JSTaggedValue> minimumFractionDigits(thread, pluralRules->GetMinimumFractionDigits());
349 JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumFractionDigits);
350 RETURN_IF_ABRUPT_COMPLETION(thread);
351 // [[MaximumFractionDigits]]
352 property = globalConst->GetHandledMaximumFractionDigitsString();
353 JSHandle<JSTaggedValue> maximumFractionDigits(thread, pluralRules->GetMaximumFractionDigits());
354 JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumFractionDigits);
355 RETURN_IF_ABRUPT_COMPLETION(thread);
356 }
357
358 // 5. Let pluralCategories be a List of Strings representing the possible results of PluralRuleSelect
359 // for the selected locale pr.[[Locale]]. This List consists of unique String values,
360 // from the the list "zero", "one", "two", "few", "many" and "other",
361 // that are relevant for the locale whose localization is specified in LDML Language Plural Rules.
362 UErrorCode status = U_ZERO_ERROR;
363 icu::PluralRules *icuPluralRules = pluralRules->GetIcuPluralRules();
364 ASSERT(icuPluralRules != nullptr);
365 std::unique_ptr<icu::StringEnumeration> categories(icuPluralRules->getKeywords(status));
366 int32_t count = categories->count(status);
367 ASSERT(U_SUCCESS(status));
368 JSHandle<TaggedArray> pluralCategories = factory->NewTaggedArray(count);
369 for (int32_t i = 0; i < count; i++) {
370 const icu::UnicodeString *category = categories->snext(status);
371 ASSERT(U_SUCCESS(status));
372 JSHandle<EcmaString> value = intl::LocaleHelper::UStringToString(thread, *category);
373 pluralCategories->Set(thread, i, value);
374 }
375
376 // 6. Perform ! CreateDataProperty(options, "pluralCategories", CreateArrayFromList(pluralCategories)).
377 property = globalConst->GetHandledPluralCategoriesString();
378 JSHandle<JSArray> jsPluralCategories = JSArray::CreateArrayFromList(thread, pluralCategories);
379 JSObject::CreateDataPropertyOrThrow(thread, options, property, JSHandle<JSTaggedValue>::Cast(jsPluralCategories));
380 }
381 } // namespace panda::ecmascript
382