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/ecma_macros.h"
22 #include "ecmascript/global_env.h"
23 #include "ecmascript/global_env_constants.h"
24 #include "ecmascript/js_array.h"
25 #include "ecmascript/js_locale.h"
26 #include "ecmascript/js_iterator.h"
27
28 #include "unicode/fieldpos.h"
29 #include "unicode/fpositer.h"
30 #include "unicode/formattedvalue.h"
31 #include "unicode/stringpiece.h"
32 #include "unicode/unistr.h"
33 #include "unicode/utf8.h"
34 #include "unicode/uloc.h"
35 #include "unicode/ustring.h"
36
37 namespace panda::ecmascript {
GetIcuListFormatter() const38 icu::ListFormatter *JSListFormat::GetIcuListFormatter() const
39 {
40 ASSERT(GetIcuLF().IsJSNativePointer());
41 auto result = JSNativePointer::Cast(GetIcuLF().GetTaggedObject())->GetExternalPointer();
42 return reinterpret_cast<icu::ListFormatter *>(result);
43 }
44
FreeIcuListFormatter(void * pointer,void * hint)45 void JSListFormat::FreeIcuListFormatter(void *pointer, [[maybe_unused]] void* hint)
46 {
47 if (pointer == nullptr) {
48 return;
49 }
50 auto icuListFormat = reinterpret_cast<icu::ListFormatter *>(pointer);
51 icuListFormat->~ListFormatter();
52 delete icuListFormat;
53 }
54
SetIcuListFormatter(JSThread * thread,const JSHandle<JSListFormat> listFormat,icu::ListFormatter * icuListFormatter,const DeleteEntryPoint & callback)55 void JSListFormat::SetIcuListFormatter(JSThread *thread, const JSHandle<JSListFormat> listFormat,
56 icu::ListFormatter *icuListFormatter, const DeleteEntryPoint &callback)
57 {
58 EcmaVM *ecmaVm = thread->GetEcmaVM();
59 ObjectFactory *factory = ecmaVm->GetFactory();
60 ASSERT(icuListFormatter != nullptr);
61 JSTaggedValue data = listFormat->GetIcuLF();
62 if (data.IsJSNativePointer()) {
63 JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
64 native->ResetExternalPointer(icuListFormatter);
65 return;
66 }
67 JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuListFormatter, callback);
68 listFormat->SetIcuLF(thread, pointer.GetTaggedValue());
69 }
70
GetAvailableLocales(JSThread * thread)71 JSHandle<TaggedArray> JSListFormat::GetAvailableLocales(JSThread *thread)
72 {
73 JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
74 JSHandle<JSTaggedValue> listFormatLocales = env->GetListFormatLocales();
75 if (!listFormatLocales->IsUndefined()) {
76 return JSHandle<TaggedArray>::Cast(listFormatLocales);
77 }
78 const char *key = "listPattern";
79 const char *path = nullptr;
80 JSHandle<TaggedArray> availableLocales = JSLocale::GetAvailableLocales(thread, key, path);
81 env->SetListFormatLocales(thread, availableLocales);
82 return availableLocales;
83 }
84
85 // 13. InitializeListFormat ( listformat, locales, options )
InitializeListFormat(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options)86 JSHandle<JSListFormat> JSListFormat::InitializeListFormat(JSThread *thread,
87 const JSHandle<JSListFormat> &listFormat,
88 const JSHandle<JSTaggedValue> &locales,
89 const JSHandle<JSTaggedValue> &options)
90 {
91 [[maybe_unused]] EcmaHandleScope scope(thread);
92 EcmaVM *ecmaVm = thread->GetEcmaVM();
93 ObjectFactory *factory = ecmaVm->GetFactory();
94 auto globalConst = thread->GlobalConstants();
95
96 // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
97 JSHandle<TaggedArray> requestedLocales = JSLocale::CanonicalizeLocaleList(thread, locales);
98 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
99
100 // 4. Let options be ? GetOptionsObject(options).
101 JSHandle<JSObject> optionsObject;
102 if (options->IsUndefined()) {
103 optionsObject = factory->CreateNullJSObject();
104 } else {
105 optionsObject = JSTaggedValue::ToObject(thread, options);
106 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
107 }
108
109 // 5. Let opt be a new Record.
110 // 6. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
111 JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
112 auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
113 thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
114 {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
115 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
116
117 // 8. Let localeData be %ListFormat%.[[LocaleData]].
118 JSHandle<TaggedArray> availableLocales;
119 if (requestedLocales->GetLength() == 0) {
120 availableLocales = factory->EmptyArray();
121 } else {
122 availableLocales = GetAvailableLocales(thread);
123 }
124
125 // 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales,
126 // opt, %ListFormat%.[[RelevantExtensionKeys]], localeData).
127 std::set<std::string> relevantExtensionKeys {""};
128 ResolvedLocale r =
129 JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
130 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
131
132 // 10. Set listFormat.[[Locale]] to r.[[locale]].
133 icu::Locale icuLocale = r.localeData;
134 JSHandle<EcmaString> localeStr = JSLocale::ToLanguageTag(thread, icuLocale);
135 listFormat->SetLocale(thread, localeStr.GetTaggedValue());
136
137 // 11. Let type be ? GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction").
138 property = globalConst->GetHandledTypeString();
139 auto type = JSLocale::GetOptionOfString<ListTypeOption>(thread, optionsObject, property,
140 {ListTypeOption::CONJUNCTION, ListTypeOption::DISJUNCTION,
141 ListTypeOption::UNIT},
142 {"conjunction", "disjunction", "unit"},
143 ListTypeOption::CONJUNCTION);
144 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
145
146 // 12. Set listFormat.[[Type]] to type.
147 listFormat->SetType(type);
148
149 // 13. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
150 property = globalConst->GetHandledStyleString();
151 auto style = JSLocale::GetOptionOfString<ListStyleOption>(thread, optionsObject, property,
152 {ListStyleOption::LONG, ListStyleOption::SHORT,
153 ListStyleOption::NARROW},
154 {"long", "short", "narrow"}, ListStyleOption::LONG);
155 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
156
157 // 14. Set listFormat.[[Style]] to style.
158 listFormat->SetStyle(style);
159
160 // 15. Let dataLocale be r.[[dataLocale]].
161 // 16. Let dataLocaleData be localeData.[[<dataLocale>]].
162 // 17. Let dataLocaleTypes be dataLocaleData.[[<type>]].
163 // 18. Set listFormat.[[Templates]] to dataLocaleTypes.[[<style>]].
164 // 19. Return listFormat.
165
166 // Trans typeOption to ICU type
167 UListFormatterType uType;
168 switch (type) {
169 case ListTypeOption::CONJUNCTION:
170 uType = ULISTFMT_TYPE_AND;
171 break;
172 case ListTypeOption::DISJUNCTION:
173 uType = ULISTFMT_TYPE_OR;
174 break;
175 case ListTypeOption::UNIT:
176 uType = ULISTFMT_TYPE_UNITS;
177 break;
178 default:
179 UNREACHABLE();
180 }
181
182 // Trans StyleOption to ICU Style
183 UListFormatterWidth uStyle;
184 switch (style) {
185 case ListStyleOption::LONG:
186 uStyle = ULISTFMT_WIDTH_WIDE;
187 break;
188 case ListStyleOption::SHORT:
189 uStyle = ULISTFMT_WIDTH_SHORT;
190 break;
191 case ListStyleOption::NARROW:
192 uStyle = ULISTFMT_WIDTH_NARROW;
193 break;
194 default:
195 UNREACHABLE();
196 }
197 UErrorCode status = U_ZERO_ERROR;
198 icu::ListFormatter *icuListFormatter = icu::ListFormatter::createInstance(icuLocale, uType, uStyle, status);
199 if (U_FAILURE(status) || icuListFormatter == nullptr) {
200 delete icuListFormatter;
201 THROW_RANGE_ERROR_AND_RETURN(thread, "icu ListFormatter Error", listFormat);
202 }
203 SetIcuListFormatter(thread, listFormat, icuListFormatter, JSListFormat::FreeIcuListFormatter);
204 return listFormat;
205 }
206
207 // 13.1.5 StringListFromIterable ( iterable )
StringListFromIterable(JSThread * thread,const JSHandle<JSTaggedValue> & iterable)208 JSHandle<JSTaggedValue> JSListFormat::StringListFromIterable(JSThread *thread, const JSHandle<JSTaggedValue> &iterable)
209 {
210 JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
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 = factory->GetJSError(ErrorType::TYPE_ERROR, "nextValue is not string");
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 = JSLocale::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 = JSLocale::IcuToString(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 = JSLocale::IcuToString(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 UNREACHABLE();
329 }
330 return result;
331 }
332
ListOptionTypeToEcmaString(JSThread * thread,ListTypeOption type)333 JSHandle<JSTaggedValue> ListOptionTypeToEcmaString(JSThread *thread, ListTypeOption type)
334 {
335 JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
336 auto globalConst = thread->GlobalConstants();
337 switch (type) {
338 case ListTypeOption::CONJUNCTION:
339 result.Update(globalConst->GetHandledConjunctionString().GetTaggedValue());
340 break;
341 case ListTypeOption::DISJUNCTION:
342 result.Update(globalConst->GetHandledDisjunctionString().GetTaggedValue());
343 break;
344 case ListTypeOption::UNIT:
345 result.Update(globalConst->GetHandledUnitString().GetTaggedValue());
346 break;
347 default:
348 UNREACHABLE();
349 }
350 return result;
351 }
352 }
353
354 // 13.1.3 FormatList ( listFormat, list )
FormatList(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSArray> & listArray)355 JSHandle<EcmaString> JSListFormat::FormatList(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
356 const JSHandle<JSArray> &listArray)
357 {
358 JSHandle<EcmaString> stringValue;
359 UErrorCode status = U_ZERO_ERROR;
360 icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray);
361 if (U_FAILURE(status)) {
362 THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", stringValue);
363 }
364 icu::UnicodeString result = formatted.toString(status);
365 if (U_FAILURE(status)) {
366 THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", stringValue);
367 }
368 stringValue = JSLocale::IcuToString(thread, result);
369 RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, stringValue);
370 // 4. Return result
371 return stringValue;
372 }
373
374 // 13.1.4 FormatListToParts ( listFormat, list )
FormatListToParts(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSArray> & listArray)375 JSHandle<JSArray> JSListFormat::FormatListToParts(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
376 const JSHandle<JSArray> &listArray)
377 {
378 UErrorCode status = U_ZERO_ERROR;
379 icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray);
380 if (U_FAILURE(status)) {
381 THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", listArray);
382 }
383 icu::UnicodeString result = formatted.toString(status);
384 if (U_FAILURE(status)) {
385 THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", listArray);
386 }
387 JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
388 FormatListToArray(thread, formatted, array, status, result);
389 RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
390 return array;
391 }
392
ResolvedOptions(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSObject> & options)393 void JSListFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
394 const JSHandle<JSObject> &options)
395 {
396 auto globalConst = thread->GlobalConstants();
397
398 // [[Locale]]
399 JSHandle<JSTaggedValue> propertyKey = globalConst->GetHandledLocaleString();
400 JSHandle<JSTaggedValue> locale(thread, listFormat->GetLocale());
401 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, locale);
402
403 // [[type]]
404 ListTypeOption type = listFormat->GetType();
405 propertyKey = globalConst->GetHandledTypeString();
406 JSHandle<JSTaggedValue> typeString = ListOptionTypeToEcmaString(thread, type);
407 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, typeString);
408
409 // [[Style]]
410 ListStyleOption style = listFormat->GetStyle();
411 propertyKey = globalConst->GetHandledStyleString();
412 JSHandle<JSTaggedValue> styleString = ListOptionStyleToEcmaString(thread, style);
413 JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, styleString);
414 }
415 } // namespace panda::ecmascript
416