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