/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/js_plural_rules.h" #include "ecmascript/object_factory-inl.h" #include "ecmascript/js_number_format.h" #include "ecmascript/checkpoint/thread_state_transition.h" namespace panda::ecmascript { constexpr int32_t STRING_SEPARATOR_LENGTH = 4; icu::number::LocalizedNumberFormatter *JSPluralRules::GetIcuNumberFormatter() const { ASSERT(GetIcuNF().IsJSNativePointer()); auto result = JSNativePointer::Cast(GetIcuNF().GetTaggedObject())->GetExternalPointer(); return reinterpret_cast(result); } void JSPluralRules::FreeIcuNumberFormatter([[maybe_unused]] void *env, void *pointer, void* hint) { if (pointer == nullptr) { return; } auto icuNumberFormatter = reinterpret_cast(pointer); icuNumberFormatter->~LocalizedNumberFormatter(); if (hint != nullptr) { reinterpret_cast(hint)->GetNativeAreaAllocator()->FreeBuffer(pointer); } } void JSPluralRules::SetIcuNumberFormatter(JSThread *thread, const JSHandle &pluralRules, const icu::number::LocalizedNumberFormatter &icuNF, const NativePointerCallback &callback) { EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); icu::number::LocalizedNumberFormatter *icuPointer = ecmaVm->GetNativeAreaAllocator()->New(icuNF); ASSERT(icuPointer != nullptr); JSTaggedValue data = pluralRules->GetIcuNF(); if (data.IsHeapObject() && data.IsJSNativePointer()) { JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject()); native->ResetExternalPointer(thread, icuPointer); return; } JSHandle pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm); pluralRules->SetIcuNF(thread, pointer.GetTaggedValue()); } icu::PluralRules *JSPluralRules::GetIcuPluralRules() const { ASSERT(GetIcuPR().IsJSNativePointer()); auto result = JSNativePointer::Cast(GetIcuPR().GetTaggedObject())->GetExternalPointer(); return reinterpret_cast(result); } void JSPluralRules::FreeIcuPluralRules([[maybe_unused]] void *env, void *pointer, void* hint) { if (pointer == nullptr) { return; } auto icuPluralRules = reinterpret_cast(pointer); icuPluralRules->~PluralRules(); if (hint != nullptr) { reinterpret_cast(hint)->GetNativeAreaAllocator()->FreeBuffer(pointer); } } void JSPluralRules::SetIcuPluralRules(JSThread *thread, const JSHandle &pluralRules, const icu::PluralRules &icuPR, const NativePointerCallback &callback) { [[maybe_unused]] EcmaHandleScope scope(thread); EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); icu::PluralRules *icuPointer = ecmaVm->GetNativeAreaAllocator()->New(icuPR); ASSERT(icuPointer != nullptr); JSTaggedValue data = pluralRules->GetIcuPR(); if (data.IsHeapObject() && data.IsJSNativePointer()) { JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject()); native->ResetExternalPointer(thread, icuPointer); return; } JSHandle pointer = factory->NewJSNativePointer(icuPointer, callback, ecmaVm); pluralRules->SetIcuPR(thread, pointer.GetTaggedValue()); } JSHandle JSPluralRules::BuildLocaleSet(JSThread *thread, const std::set &icuAvailableLocales) { EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); JSHandle locales = factory->NewTaggedArray(icuAvailableLocales.size()); int32_t index = 0; for (const std::string &locale : icuAvailableLocales) { JSHandle localeStr = factory->NewFromStdString(locale); locales->Set(thread, index++, localeStr); } return locales; } bool GetNextLocale(icu::StringEnumeration *locales, std::string &localeStr, int32_t *len) { UErrorCode status = U_ZERO_ERROR; const char *locale = nullptr; locale = locales->next(len, status); if (!U_SUCCESS(status) || locale == nullptr) { localeStr = ""; return false; } localeStr = std::string(locale); return true; } JSHandle JSPluralRules::GetAvailableLocales(JSThread *thread) { UErrorCode status = U_ZERO_ERROR; std::unique_ptr locales(icu::PluralRules::getAvailableLocales(status)); ASSERT(U_SUCCESS(status)); std::set set; std::string localeStr; int32_t len = 0; { ThreadNativeScope nativeScope(thread); while (GetNextLocale(locales.get(), localeStr, &len)) { if (len >= STRING_SEPARATOR_LENGTH) { std::replace(localeStr.begin(), localeStr.end(), '_', '-'); } set.insert(localeStr); } } return BuildLocaleSet(thread, set); } // InitializePluralRules ( pluralRules, locales, options ) JSHandle JSPluralRules::InitializePluralRules(JSThread *thread, const JSHandle &pluralRules, const JSHandle &locales, const JSHandle &options) { EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); auto globalConst = thread->GlobalConstants(); // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). JSHandle requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread); // 2&3. If options is undefined, then Let options be ObjectCreate(null). else Let options be ? ToObject(options). JSHandle prOptions; if (!options->IsUndefined()) { prOptions = JSTaggedValue::ToObject(thread, options); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread); } else { prOptions = factory->CreateNullJSObject(); } // 5. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). LocaleMatcherOption matcher = JSLocale::GetOptionOfString(thread, prOptions, globalConst->GetHandledLocaleMatcherString(), {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT}, {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread); // 7. Let t be ? GetOption(options, "type", "string", « "cardinal", "ordinal" », "cardinal"). JSHandle property = JSHandle::Cast(globalConst->GetHandledTypeString()); TypeOption type = JSLocale::GetOptionOfString(thread, prOptions, property, { TypeOption::CARDINAL, TypeOption::ORDINAL }, { "cardinal", "ordinal" }, TypeOption::CARDINAL); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread); // set pluralRules.[[type]] to type pluralRules->SetType(type); // Let r be ResolveLocale(%PluralRules%.[[AvailableLocales]], requestedLocales, opt, // %PluralRules%.[[RelevantExtensionKeys]], localeData). JSHandle availableLocales; if (requestedLocales->GetLength() == 0) { availableLocales = factory->EmptyArray(); } else { availableLocales = GetAvailableLocales(thread); } std::set relevantExtensionKeys{""}; ResolvedLocale r = JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread); icu::Locale icuLocale = r.localeData; // Get ICU numberFormatter with given locale icu::number::LocalizedNumberFormatter icuNumberFormatter = icu::number::NumberFormatter::withLocale(icuLocale).roundingMode(UNUM_ROUND_HALFUP); bool success = true; UErrorCode status = U_ZERO_ERROR; UPluralType icuType = UPLURAL_TYPE_CARDINAL; // Trans typeOption to ICU typeOption switch (type) { case TypeOption::ORDINAL: icuType = UPLURAL_TYPE_ORDINAL; break; case TypeOption::CARDINAL: icuType = UPLURAL_TYPE_CARDINAL; break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } std::unique_ptr icuPluralRules(icu::PluralRules::forLocale(icuLocale, icuType, status)); if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion) success = false; } // Trans typeOption to ICU typeOption if (!success || icuPluralRules == nullptr) { icu::Locale noExtensionLocale(icuLocale.getBaseName()); status = U_ZERO_ERROR; switch (type) { case TypeOption::ORDINAL: icuType = UPLURAL_TYPE_ORDINAL; break; case TypeOption::CARDINAL: icuType = UPLURAL_TYPE_CARDINAL; break; default: LOG_ECMA(FATAL) << "this branch is unreachable"; UNREACHABLE(); } icuPluralRules.reset(icu::PluralRules::forLocale(icuLocale, icuType, status)); } if (U_FAILURE(status) || icuPluralRules == nullptr) { // NOLINT(readability-implicit-bool-conversion) THROW_RANGE_ERROR_AND_RETURN(thread, "cannot create icuPluralRules", pluralRules); } // 9. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3, "standard"). JSLocale::SetNumberFormatDigitOptions(thread, pluralRules, JSHandle::Cast(prOptions), MNFD_DEFAULT, MXFD_DEFAULT, NotationOption::STANDARD); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread); icuNumberFormatter = JSNumberFormat::SetICUFormatterDigitOptions(icuNumberFormatter, pluralRules); // Set pluralRules.[[IcuPluralRules]] to icuPluralRules SetIcuPluralRules(thread, pluralRules, *icuPluralRules, JSPluralRules::FreeIcuPluralRules); // Set pluralRules.[[IcuNumberFormat]] to icuNumberFormatter SetIcuNumberFormatter(thread, pluralRules, icuNumberFormatter, JSPluralRules::FreeIcuNumberFormatter); // 12. Set pluralRules.[[Locale]] to the value of r.[[locale]]. JSHandle localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale); RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSPluralRules, thread); pluralRules->SetLocale(thread, localeStr.GetTaggedValue()); // 13. Return pluralRules. return pluralRules; } JSHandle FormatNumericToString(JSThread *thread, const icu::number::LocalizedNumberFormatter *icuFormatter, const icu::PluralRules *icuPluralRules, double n) { UErrorCode status = U_ZERO_ERROR; icu::number::FormattedNumber formatted = icuFormatter->formatDouble(n, status); if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion) JSHandle exception(thread, JSTaggedValue::Exception()); THROW_RANGE_ERROR_AND_RETURN(thread, "invalid resolve number", JSHandle::Cast(exception)); } icu::UnicodeString uString = icuPluralRules->select(formatted, status); if (U_FAILURE(status)) { // NOLINT(readability-implicit-bool-conversion) JSHandle exception(thread, JSTaggedValue::Exception()); THROW_RANGE_ERROR_AND_RETURN(thread, "invalid resolve number", JSHandle::Cast(exception)); } ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle result = factory->NewFromUtf16(reinterpret_cast(uString.getBuffer()), uString.length()); return result; } JSHandle JSPluralRules::ResolvePlural(JSThread *thread, const JSHandle &pluralRules, double n) { icu::PluralRules *icuPluralRules = pluralRules->GetIcuPluralRules(); icu::number::LocalizedNumberFormatter *icuFormatter = pluralRules->GetIcuNumberFormatter(); if (icuPluralRules == nullptr || icuFormatter == nullptr) { return JSHandle(thread, JSTaggedValue::Undefined()); } JSHandle result = FormatNumericToString(thread, icuFormatter, icuPluralRules, n); RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread); return result; } void JSPluralRules::ResolvedOptions(JSThread *thread, const JSHandle &pluralRules, const JSHandle &options) { EcmaVM *ecmaVm = thread->GetEcmaVM(); ObjectFactory *factory = ecmaVm->GetFactory(); auto globalConst = thread->GlobalConstants(); // [[Locale]] JSHandle property = JSHandle::Cast(globalConst->GetHandledLocaleString()); JSHandle locale(thread, pluralRules->GetLocale()); PropertyDescriptor localeDesc(thread, JSHandle::Cast(locale), true, true, true); JSObject::DefineOwnProperty(thread, options, property, localeDesc); // [[type]] property = JSHandle::Cast(globalConst->GetHandledTypeString()); JSHandle typeValue; if (pluralRules->GetType() == TypeOption::CARDINAL) { typeValue = globalConst->GetHandledCardinalString(); } else { typeValue = globalConst->GetHandledOrdinalString(); } PropertyDescriptor typeDesc(thread, typeValue, true, true, true); JSObject::DefineOwnProperty(thread, options, property, typeDesc); // [[MinimumIntegerDigits]] property = JSHandle::Cast(globalConst->GetHandledMinimumIntegerDigitsString()); JSHandle minimumIntegerDigits(thread, pluralRules->GetMinimumIntegerDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumIntegerDigits); RETURN_IF_ABRUPT_COMPLETION(thread); RoundingType roundingType = pluralRules->GetRoundingType(); if (roundingType == RoundingType::SIGNIFICANTDIGITS) { // [[MinimumSignificantDigits]] property = globalConst->GetHandledMinimumSignificantDigitsString(); JSHandle minimumSignificantDigits(thread, pluralRules->GetMinimumSignificantDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumSignificantDigits); RETURN_IF_ABRUPT_COMPLETION(thread); // [[MaximumSignificantDigits]] property = globalConst->GetHandledMaximumSignificantDigitsString(); JSHandle maximumSignificantDigits(thread, pluralRules->GetMaximumSignificantDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumSignificantDigits); RETURN_IF_ABRUPT_COMPLETION(thread); } else { // [[MinimumFractionDigits]] property = globalConst->GetHandledMinimumFractionDigitsString(); JSHandle minimumFractionDigits(thread, pluralRules->GetMinimumFractionDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, minimumFractionDigits); RETURN_IF_ABRUPT_COMPLETION(thread); // [[MaximumFractionDigits]] property = globalConst->GetHandledMaximumFractionDigitsString(); JSHandle maximumFractionDigits(thread, pluralRules->GetMaximumFractionDigits()); JSObject::CreateDataPropertyOrThrow(thread, options, property, maximumFractionDigits); RETURN_IF_ABRUPT_COMPLETION(thread); } // 5. Let pluralCategories be a List of Strings representing the possible results of PluralRuleSelect // for the selected locale pr.[[Locale]]. This List consists of unique String values, // from the the list "zero", "one", "two", "few", "many" and "other", // that are relevant for the locale whose localization is specified in LDML Language Plural Rules. UErrorCode status = U_ZERO_ERROR; icu::PluralRules *icuPluralRules = pluralRules->GetIcuPluralRules(); ASSERT(icuPluralRules != nullptr); std::unique_ptr categories(icuPluralRules->getKeywords(status)); int32_t count = categories->count(status); ASSERT(U_SUCCESS(status)); JSHandle pluralCategories = factory->NewTaggedArray(count); for (int32_t i = 0; i < count; i++) { const icu::UnicodeString *category = categories->snext(status); ASSERT(U_SUCCESS(status)); JSHandle value = intl::LocaleHelper::UStringToString(thread, *category); pluralCategories->Set(thread, i, value); } // 6. Perform ! CreateDataProperty(options, "pluralCategories", CreateArrayFromList(pluralCategories)). property = globalConst->GetHandledPluralCategoriesString(); JSHandle jsPluralCategories = JSArray::CreateArrayFromList(thread, pluralCategories); JSObject::CreateDataPropertyOrThrow(thread, options, property, JSHandle::Cast(jsPluralCategories)); RETURN_IF_ABRUPT_COMPLETION(thread); } } // namespace panda::ecmascript