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