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