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