• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/object_factory-inl.h"
19 #include "ecmascript/js_number_format.h"
20 #include "ecmascript/checkpoint/thread_state_transition.h"
21 
22 namespace panda::ecmascript {
23 constexpr int32_t STRING_SEPARATOR_LENGTH = 4;
24 
GetIcuNumberFormatter() const25 icu::number::LocalizedNumberFormatter *JSPluralRules::GetIcuNumberFormatter() const
26 {
27     ASSERT(GetIcuNF().IsJSNativePointer());
28     auto result = JSNativePointer::Cast(GetIcuNF().GetTaggedObject())->GetExternalPointer();
29     return reinterpret_cast<icu::number::LocalizedNumberFormatter *>(result);
30 }
31 
FreeIcuNumberFormatter(void * env,void * pointer,void * hint)32 void JSPluralRules::FreeIcuNumberFormatter([[maybe_unused]] void *env, void *pointer, void* hint)
33 {
34     if (pointer == nullptr) {
35         return;
36     }
37     auto icuNumberFormatter = reinterpret_cast<icu::number::LocalizedNumberFormatter *>(pointer);
38     icuNumberFormatter->~LocalizedNumberFormatter();
39     if (hint != nullptr) {
40         reinterpret_cast<EcmaVM *>(hint)->GetNativeAreaAllocator()->FreeBuffer(pointer);
41     }
42 }
43 
SetIcuNumberFormatter(JSThread * thread,const JSHandle<JSPluralRules> & pluralRules,const icu::number::LocalizedNumberFormatter & icuNF,const NativePointerCallback & callback)44 void JSPluralRules::SetIcuNumberFormatter(JSThread *thread, const JSHandle<JSPluralRules> &pluralRules,
45     const icu::number::LocalizedNumberFormatter &icuNF, const NativePointerCallback &callback)
46 {
47     EcmaVM *ecmaVm = thread->GetEcmaVM();
48     ObjectFactory *factory = ecmaVm->GetFactory();
49 
50     icu::number::LocalizedNumberFormatter *icuPointer =
51         ecmaVm->GetNativeAreaAllocator()->New<icu::number::LocalizedNumberFormatter>(icuNF);
52     ASSERT(icuPointer != nullptr);
53     JSTaggedValue data = pluralRules->GetIcuNF();
54     if (data.IsHeapObject() && data.IsJSNativePointer()) {
55         JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
56         native->ResetExternalPointer(thread, icuPointer);
57         return;
58     }
59     JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm);
60     pluralRules->SetIcuNF(thread, pointer.GetTaggedValue());
61 }
62 
GetIcuPluralRules() const63 icu::PluralRules *JSPluralRules::GetIcuPluralRules() const
64 {
65     ASSERT(GetIcuPR().IsJSNativePointer());
66     auto result = JSNativePointer::Cast(GetIcuPR().GetTaggedObject())->GetExternalPointer();
67     return reinterpret_cast<icu::PluralRules *>(result);
68 }
69 
FreeIcuPluralRules(void * env,void * pointer,void * hint)70 void JSPluralRules::FreeIcuPluralRules([[maybe_unused]] void *env, void *pointer, void* hint)
71 {
72     if (pointer == nullptr) {
73         return;
74     }
75     auto icuPluralRules = reinterpret_cast<icu::PluralRules *>(pointer);
76     icuPluralRules->~PluralRules();
77     if (hint != nullptr) {
78         reinterpret_cast<EcmaVM *>(hint)->GetNativeAreaAllocator()->FreeBuffer(pointer);
79     }
80 }
81 
SetIcuPluralRules(JSThread * thread,const JSHandle<JSPluralRules> & pluralRules,const icu::PluralRules & icuPR,const NativePointerCallback & callback)82 void JSPluralRules::SetIcuPluralRules(JSThread *thread, const JSHandle<JSPluralRules> &pluralRules,
83     const icu::PluralRules &icuPR, const NativePointerCallback &callback)
84 {
85     [[maybe_unused]] EcmaHandleScope scope(thread);
86     EcmaVM *ecmaVm = thread->GetEcmaVM();
87     ObjectFactory *factory = ecmaVm->GetFactory();
88 
89     icu::PluralRules *icuPointer = ecmaVm->GetNativeAreaAllocator()->New<icu::PluralRules>(icuPR);
90     ASSERT(icuPointer != nullptr);
91     JSTaggedValue data = pluralRules->GetIcuPR();
92     if (data.IsHeapObject() && data.IsJSNativePointer()) {
93         JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
94         native->ResetExternalPointer(thread, icuPointer);
95         return;
96     }
97     JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm);
98     pluralRules->SetIcuPR(thread, pointer.GetTaggedValue());
99 }
100 
BuildLocaleSet(JSThread * thread,const std::set<std::string> & icuAvailableLocales)101 JSHandle<TaggedArray> JSPluralRules::BuildLocaleSet(JSThread *thread, const std::set<std::string> &icuAvailableLocales)
102 {
103     EcmaVM *ecmaVm = thread->GetEcmaVM();
104     ObjectFactory *factory = ecmaVm->GetFactory();
105     JSHandle<TaggedArray> locales = factory->NewTaggedArray(icuAvailableLocales.size());
106     int32_t index = 0;
107 
108     for (const std::string &locale : icuAvailableLocales) {
109         JSHandle<EcmaString> localeStr = factory->NewFromStdString(locale);
110         locales->Set(thread, index++, localeStr);
111     }
112     return locales;
113 }
114 
GetNextLocale(icu::StringEnumeration * locales,std::string & localeStr,int32_t * len)115 bool GetNextLocale(icu::StringEnumeration *locales, std::string &localeStr, int32_t *len)
116 {
117     UErrorCode status = U_ZERO_ERROR;
118     const char *locale = nullptr;
119     locale = locales->next(len, status);
120     if (!U_SUCCESS(status) || locale == nullptr) {
121         localeStr = "";
122         return false;
123     }
124     localeStr = std::string(locale);
125     return true;
126 }
127 
GetAvailableLocales(JSThread * thread)128 JSHandle<TaggedArray> JSPluralRules::GetAvailableLocales(JSThread *thread)
129 {
130     UErrorCode status = U_ZERO_ERROR;
131     std::unique_ptr<icu::StringEnumeration> locales(icu::PluralRules::getAvailableLocales(status));
132     ASSERT(U_SUCCESS(status));
133     std::set<std::string> set;
134     std::string localeStr;
135     int32_t len = 0;
136     {
137         ThreadNativeScope nativeScope(thread);
138         while (GetNextLocale(locales.get(), localeStr, &len)) {
139             if (len >= STRING_SEPARATOR_LENGTH) {
140                 std::replace(localeStr.begin(), localeStr.end(), '_', '-');
141             }
142             set.insert(localeStr);
143         }
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     RETURN_IF_ABRUPT_COMPLETION(thread);
381 }
382 }  // namespace panda::ecmascript
383