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
27 namespace panda::ecmascript {
GetIcuListFormatter() const28 icu::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
FreeIcuListFormatter([[maybe_unused]] void *env, void *pointer, [[maybe_unused]] void* hint)35 void 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
SetIcuListFormatter(JSThread *thread, const JSHandle<JSListFormat> listFormat, icu::ListFormatter *icuListFormatter, const NativePointerCallback &callback)45 void 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
GetAvailableLocales(JSThread *thread)61 JSHandle<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 )
InitializeListFormat(JSThread *thread, const JSHandle<JSListFormat> &listFormat, const JSHandle<JSTaggedValue> &locales, const JSHandle<JSTaggedValue> &options)77 JSHandle<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 )
StringListFromIterable(JSThread *thread, const JSHandle<JSTaggedValue> &iterable)207 JSHandle<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
255 namespace {
ToUnicodeStringArray(JSThread *thread, const JSHandle<JSArray> &array)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
GetIcuFormatted(JSThread *thread, const JSHandle<JSListFormat> &listFormat, const JSHandle<JSArray> &listArray)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
FormatListToArray(JSThread *thread, const icu::FormattedList &formatted, const JSHandle<JSArray> &receiver, UErrorCode &status, icu::UnicodeString &listString)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
ListOptionStyleToEcmaString(JSThread *thread, ListStyleOption style)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
ListOptionTypeToEcmaString(JSThread *thread, ListTypeOption type)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 )
FormatList(JSThread *thread, const JSHandle<JSListFormat> &listFormat, const JSHandle<JSArray> &listArray)358 JSHandle<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 )
FormatListToParts(JSThread *thread, const JSHandle<JSListFormat> &listFormat, const JSHandle<JSArray> &listArray)378 JSHandle<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
ResolvedOptions(JSThread *thread, const JSHandle<JSListFormat> &listFormat, const JSHandle<JSObject> &options)397 void 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