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
27namespace panda::ecmascript {
28icu::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
35void 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
45void 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
61JSHandle<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 )
77JSHandle<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 )
207JSHandle<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
255namespace {
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
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
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
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
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 )
358JSHandle<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 )
378JSHandle<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
397void 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