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