1/* 2 * Copyright (c) 2022 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_list_format.h" 17 18#include <cstring> 19#include <vector> 20 21#include "ecmascript/intl/locale_helper.h" 22#include "ecmascript/global_env.h" 23#include "ecmascript/js_iterator.h" 24#include "ecmascript/object_factory-inl.h" 25 26 27namespace panda::ecmascript { 28icu::ListFormatter *JSListFormat::GetIcuListFormatter() const 29{ 30 ASSERT(GetIcuLF().IsJSNativePointer()); 31 auto result = JSNativePointer::Cast(GetIcuLF().GetTaggedObject())->GetExternalPointer(); 32 return reinterpret_cast<icu::ListFormatter *>(result); 33} 34 35void JSListFormat::FreeIcuListFormatter([[maybe_unused]] void *env, void *pointer, [[maybe_unused]] void* hint) 36{ 37 if (pointer == nullptr) { 38 return; 39 } 40 auto icuListFormat = reinterpret_cast<icu::ListFormatter *>(pointer); 41 icuListFormat->~ListFormatter(); 42 delete icuListFormat; 43} 44 45void JSListFormat::SetIcuListFormatter(JSThread *thread, const JSHandle<JSListFormat> listFormat, 46 icu::ListFormatter *icuListFormatter, const NativePointerCallback &callback) 47{ 48 EcmaVM *ecmaVm = thread->GetEcmaVM(); 49 ObjectFactory *factory = ecmaVm->GetFactory(); 50 ASSERT(icuListFormatter != nullptr); 51 JSTaggedValue data = listFormat->GetIcuLF(); 52 if (data.IsJSNativePointer()) { 53 JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject()); 54 native->ResetExternalPointer(thread, icuListFormatter); 55 return; 56 } 57 JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuListFormatter, callback); 58 listFormat->SetIcuLF(thread, pointer.GetTaggedValue()); 59} 60 61JSHandle<TaggedArray> JSListFormat::GetAvailableLocales(JSThread *thread) 62{ 63 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv(); 64 JSHandle<JSTaggedValue> listFormatLocales = env->GetListFormatLocales(); 65 if (!listFormatLocales->IsUndefined()) { 66 return JSHandle<TaggedArray>::Cast(listFormatLocales); 67 } 68 const char *key = "listPattern"; 69 const char *path = nullptr; 70 std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path); 71 JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales); 72 env->SetListFormatLocales(thread, availableLocales); 73 return availableLocales; 74} 75 76// 13. InitializeListFormat ( listformat, locales, options ) 77JSHandle<JSListFormat> JSListFormat::InitializeListFormat(JSThread *thread, 78 const JSHandle<JSListFormat> &listFormat, 79 const JSHandle<JSTaggedValue> &locales, 80 const JSHandle<JSTaggedValue> &options) 81{ 82 [[maybe_unused]] EcmaHandleScope scope(thread); 83 EcmaVM *ecmaVm = thread->GetEcmaVM(); 84 ObjectFactory *factory = ecmaVm->GetFactory(); 85 auto globalConst = thread->GlobalConstants(); 86 87 // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). 88 JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales); 89 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread); 90 91 // 4. Let options be ? GetOptionsObject(options). 92 JSHandle<JSObject> optionsObject; 93 if (options->IsUndefined()) { 94 optionsObject = factory->CreateNullJSObject(); 95 } else if (!options->IsJSObject()) { 96 THROW_TYPE_ERROR_AND_RETURN(thread, "options is not Object", listFormat); 97 } else { 98 optionsObject = JSTaggedValue::ToObject(thread, options); 99 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread); 100 } 101 102 // 5. Let opt be a new Record. 103 // 6. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). 104 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString(); 105 auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>( 106 thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT}, 107 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT); 108 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread); 109 110 // 8. Let localeData be %ListFormat%.[[LocaleData]]. 111 JSHandle<TaggedArray> availableLocales; 112 if (requestedLocales->GetLength() == 0) { 113 availableLocales = factory->EmptyArray(); 114 } else { 115 availableLocales = GetAvailableLocales(thread); 116 } 117 118 // 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales, 119 // opt, %ListFormat%.[[RelevantExtensionKeys]], localeData). 120 std::set<std::string> relevantExtensionKeys {""}; 121 ResolvedLocale r = 122 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys); 123 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread); 124 125 // 10. Set listFormat.[[Locale]] to r.[[locale]]. 126 icu::Locale icuLocale = r.localeData; 127 JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale); 128 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread); 129 listFormat->SetLocale(thread, localeStr.GetTaggedValue()); 130 131 // 11. Let type be ? GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction"). 132 property = globalConst->GetHandledTypeString(); 133 auto type = JSLocale::GetOptionOfString<ListTypeOption>(thread, optionsObject, property, 134 {ListTypeOption::CONJUNCTION, ListTypeOption::DISJUNCTION, 135 ListTypeOption::UNIT}, 136 {"conjunction", "disjunction", "unit"}, 137 ListTypeOption::CONJUNCTION); 138 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread); 139 140 // 12. Set listFormat.[[Type]] to type. 141 listFormat->SetType(type); 142 143 // 13. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long"). 144 property = globalConst->GetHandledStyleString(); 145 auto style = JSLocale::GetOptionOfString<ListStyleOption>(thread, optionsObject, property, 146 {ListStyleOption::LONG, ListStyleOption::SHORT, 147 ListStyleOption::NARROW}, 148 {"long", "short", "narrow"}, ListStyleOption::LONG); 149 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread); 150 151 // 14. Set listFormat.[[Style]] to style. 152 listFormat->SetStyle(style); 153 154 // 15. Let dataLocale be r.[[dataLocale]]. 155 // 16. Let dataLocaleData be localeData.[[<dataLocale>]]. 156 // 17. Let dataLocaleTypes be dataLocaleData.[[<type>]]. 157 // 18. Set listFormat.[[Templates]] to dataLocaleTypes.[[<style>]]. 158 // 19. Return listFormat. 159 160 // Trans typeOption to ICU type 161 UListFormatterType uType; 162 switch (type) { 163 case ListTypeOption::CONJUNCTION: 164 uType = ULISTFMT_TYPE_AND; 165 break; 166 case ListTypeOption::DISJUNCTION: 167 uType = ULISTFMT_TYPE_OR; 168 break; 169 case ListTypeOption::UNIT: 170 uType = ULISTFMT_TYPE_UNITS; 171 break; 172 default: 173 LOG_ECMA(FATAL) << "this branch is unreachable"; 174 UNREACHABLE(); 175 } 176 177 // Trans StyleOption to ICU Style 178 UListFormatterWidth uStyle; 179 switch (style) { 180 case ListStyleOption::LONG: 181 uStyle = ULISTFMT_WIDTH_WIDE; 182 break; 183 case ListStyleOption::SHORT: 184 uStyle = ULISTFMT_WIDTH_SHORT; 185 break; 186 case ListStyleOption::NARROW: 187 uStyle = ULISTFMT_WIDTH_NARROW; 188 break; 189 default: 190 LOG_ECMA(FATAL) << "this branch is unreachable"; 191 UNREACHABLE(); 192 } 193 UErrorCode status = U_ZERO_ERROR; 194 icu::ListFormatter *icuListFormatter = icu::ListFormatter::createInstance(icuLocale, uType, uStyle, status); 195 if (U_FAILURE(status) || icuListFormatter == nullptr) { 196 delete icuListFormatter; 197 if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) { 198 THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", listFormat); 199 } 200 THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::ListFormatter failed", listFormat); 201 } 202 SetIcuListFormatter(thread, listFormat, icuListFormatter, JSListFormat::FreeIcuListFormatter); 203 return listFormat; 204} 205 206// 13.1.5 StringListFromIterable ( iterable ) 207JSHandle<JSTaggedValue> JSListFormat::StringListFromIterable(JSThread *thread, const JSHandle<JSTaggedValue> &iterable) 208{ 209 JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); 210 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); 211 JSHandle<JSTaggedValue> arrayList = JSHandle<JSTaggedValue>::Cast(array); 212 // 1. If iterable is undefined, then 213 // a. Return a new empty List. 214 if (iterable->IsUndefined()) { 215 return arrayList; 216 } 217 // 2. Let iteratorRecord be ? GetIterator(iterable). 218 JSHandle<JSTaggedValue> iteratorRecord(JSIterator::GetIterator(thread, iterable)); 219 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); 220 // 3. Let list be a new empty List. 221 // 4. Let next be true. 222 JSHandle<JSTaggedValue> next(thread, JSTaggedValue::True()); 223 // 5. Repeat, while next is not false, 224 // a. Set next to ? IteratorStep(iteratorRecord). 225 // b. If next is not false, then 226 // i. Let nextValue be ? IteratorValue(next). 227 // ii. If Type(nextValue) is not String, then 228 // 1. Let error be ThrowCompletion(a newly created TypeError object). 229 // 2. Return ? IteratorClose(iteratorRecord, error). 230 // iii. Append nextValue to the end of the List list. 231 uint32_t k = 0; 232 while (!next->IsFalse()) { 233 next = JSIterator::IteratorStep(thread, iteratorRecord); 234 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); 235 if (!next->IsFalse()) { 236 JSHandle<JSTaggedValue> nextValue(JSIterator::IteratorValue(thread, next)); 237 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread); 238 if (!nextValue->IsString()) { 239 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); 240 JSHandle<JSObject> typeError = 241 factory->GetJSError(ErrorType::TYPE_ERROR, "nextValue is not string", StackCheck::NO); 242 JSHandle<JSTaggedValue> error( 243 factory->NewCompletionRecord(CompletionRecordType::THROW, JSHandle<JSTaggedValue>(typeError))); 244 JSTaggedValue result = JSIterator::IteratorClose(thread, iteratorRecord, error).GetTaggedValue(); 245 THROW_TYPE_ERROR_AND_RETURN(thread, "type error", JSHandle<JSTaggedValue>(thread, result)); 246 } 247 JSArray::FastSetPropertyByValue(thread, arrayList, k, nextValue); 248 k++; 249 } 250 } 251 // 6. Return list. 252 return arrayList; 253} 254 255namespace { 256 std::vector<icu::UnicodeString> ToUnicodeStringArray(JSThread *thread, const JSHandle<JSArray> &array) 257 { 258 uint32_t length = array->GetArrayLength(); 259 std::vector<icu::UnicodeString> result; 260 for (uint32_t k = 0; k < length; k++) { 261 JSHandle<JSTaggedValue> listArray = JSHandle<JSTaggedValue>::Cast(array); 262 JSHandle<JSTaggedValue> kValue = JSArray::FastGetPropertyByValue(thread, listArray, k); 263 ASSERT(kValue->IsString()); 264 JSHandle<EcmaString> kValueString = JSTaggedValue::ToString(thread, kValue); 265 std::string stdString = intl::LocaleHelper::ConvertToStdString(kValueString); 266 icu::StringPiece sp(stdString); 267 icu::UnicodeString uString = icu::UnicodeString::fromUTF8(sp); 268 result.push_back(uString); 269 } 270 return result; 271 } 272 273 icu::FormattedList GetIcuFormatted(JSThread *thread, const JSHandle<JSListFormat> &listFormat, 274 const JSHandle<JSArray> &listArray) 275 { 276 icu::ListFormatter *icuListFormat = listFormat->GetIcuListFormatter(); 277 ASSERT(icuListFormat != nullptr); 278 std::vector<icu::UnicodeString> usArray = ToUnicodeStringArray(thread, listArray); 279 UErrorCode status = U_ZERO_ERROR; 280 icu::FormattedList formatted = icuListFormat->formatStringsToValue(usArray.data(), 281 static_cast<int32_t>(usArray.size()), 282 status); 283 return formatted; 284 } 285 286 void FormatListToArray(JSThread *thread, const icu::FormattedList &formatted, const JSHandle<JSArray> &receiver, 287 UErrorCode &status, icu::UnicodeString &listString) 288 { 289 icu::ConstrainedFieldPosition cfpo; 290 cfpo.constrainCategory(UFIELD_CATEGORY_LIST); 291 auto globalConst = thread->GlobalConstants(); 292 JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined()); 293 int index = 0; 294 while (formatted.nextPosition(cfpo, status) && U_SUCCESS(status)) { 295 int32_t fieldId = cfpo.getField(); 296 int32_t start = cfpo.getStart(); 297 int32_t limit = cfpo.getLimit(); 298 if (static_cast<UListFormatterField>(fieldId) == ULISTFMT_ELEMENT_FIELD) { 299 JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, listString, start, limit); 300 typeString.Update(globalConst->GetElementString()); 301 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring)); 302 RETURN_IF_ABRUPT_COMPLETION(thread); 303 index++; 304 } else { 305 JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, listString, start, limit); 306 typeString.Update(globalConst->GetLiteralString()); 307 JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring)); 308 RETURN_IF_ABRUPT_COMPLETION(thread); 309 index++; 310 } 311 } 312 } 313 314 JSHandle<JSTaggedValue> ListOptionStyleToEcmaString(JSThread *thread, ListStyleOption style) 315 { 316 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined()); 317 auto globalConst = thread->GlobalConstants(); 318 switch (style) { 319 case ListStyleOption::LONG: 320 result.Update(globalConst->GetHandledLongString().GetTaggedValue()); 321 break; 322 case ListStyleOption::SHORT: 323 result.Update(globalConst->GetHandledShortString().GetTaggedValue()); 324 break; 325 case ListStyleOption::NARROW: 326 result.Update(globalConst->GetHandledNarrowString().GetTaggedValue()); 327 break; 328 default: 329 LOG_ECMA(FATAL) << "this branch is unreachable"; 330 UNREACHABLE(); 331 } 332 return result; 333 } 334 335 JSHandle<JSTaggedValue> ListOptionTypeToEcmaString(JSThread *thread, ListTypeOption type) 336 { 337 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined()); 338 auto globalConst = thread->GlobalConstants(); 339 switch (type) { 340 case ListTypeOption::CONJUNCTION: 341 result.Update(globalConst->GetHandledConjunctionString().GetTaggedValue()); 342 break; 343 case ListTypeOption::DISJUNCTION: 344 result.Update(globalConst->GetHandledDisjunctionString().GetTaggedValue()); 345 break; 346 case ListTypeOption::UNIT: 347 result.Update(globalConst->GetHandledUnitString().GetTaggedValue()); 348 break; 349 default: 350 LOG_ECMA(FATAL) << "this branch is unreachable"; 351 UNREACHABLE(); 352 } 353 return result; 354 } 355} 356 357// 13.1.3 FormatList ( listFormat, list ) 358JSHandle<EcmaString> JSListFormat::FormatList(JSThread *thread, const JSHandle<JSListFormat> &listFormat, 359 const JSHandle<JSArray> &listArray) 360{ 361 JSHandle<EcmaString> stringValue; 362 UErrorCode status = U_ZERO_ERROR; 363 icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray); 364 if (U_FAILURE(status)) { 365 THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", stringValue); 366 } 367 icu::UnicodeString result = formatted.toString(status); 368 if (U_FAILURE(status)) { 369 THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", stringValue); 370 } 371 stringValue = intl::LocaleHelper::UStringToString(thread, result); 372 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, stringValue); 373 // 4. Return result 374 return stringValue; 375} 376 377// 13.1.4 FormatListToParts ( listFormat, list ) 378JSHandle<JSArray> JSListFormat::FormatListToParts(JSThread *thread, const JSHandle<JSListFormat> &listFormat, 379 const JSHandle<JSArray> &listArray) 380{ 381 UErrorCode status = U_ZERO_ERROR; 382 icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray); 383 if (U_FAILURE(status)) { 384 THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", listArray); 385 } 386 icu::UnicodeString result = formatted.toString(status); 387 if (U_FAILURE(status)) { 388 THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", listArray); 389 } 390 JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0))); 391 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 392 FormatListToArray(thread, formatted, array, status, result); 393 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread); 394 return array; 395} 396 397void JSListFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSListFormat> &listFormat, 398 const JSHandle<JSObject> &options) 399{ 400 auto globalConst = thread->GlobalConstants(); 401 402 // [[Locale]] 403 JSHandle<JSTaggedValue> propertyKey = globalConst->GetHandledLocaleString(); 404 JSHandle<JSTaggedValue> locale(thread, listFormat->GetLocale()); 405 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, locale); 406 RETURN_IF_ABRUPT_COMPLETION(thread); 407 408 // [[type]] 409 ListTypeOption type = listFormat->GetType(); 410 propertyKey = globalConst->GetHandledTypeString(); 411 JSHandle<JSTaggedValue> typeString = ListOptionTypeToEcmaString(thread, type); 412 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, typeString); 413 RETURN_IF_ABRUPT_COMPLETION(thread); 414 415 // [[Style]] 416 ListStyleOption style = listFormat->GetStyle(); 417 propertyKey = globalConst->GetHandledStyleString(); 418 JSHandle<JSTaggedValue> styleString = ListOptionStyleToEcmaString(thread, style); 419 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, styleString); 420 RETURN_IF_ABRUPT_COMPLETION(thread); 421} 422} // namespace panda::ecmascript 423