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 22namespace panda::ecmascript { 23constexpr int32_t STRING_SEPARATOR_LENGTH = 4; 24 25icu::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 32void 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 44void 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 63icu::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 70void 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 82void 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 101JSHandle<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 115bool 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 128JSHandle<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 ) 149JSHandle<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 268JSHandle<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} 289JSHandle<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 303void 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