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