• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 namespace panda::ecmascript {
GetIcuListFormatter() const27 icu::ListFormatter *JSListFormat::GetIcuListFormatter() const
28 {
29     ASSERT(GetIcuLF().IsJSNativePointer());
30     auto result = JSNativePointer::Cast(GetIcuLF().GetTaggedObject())->GetExternalPointer();
31     return reinterpret_cast<icu::ListFormatter *>(result);
32 }
33 
FreeIcuListFormatter(void * env,void * pointer,void * hint)34 void JSListFormat::FreeIcuListFormatter([[maybe_unused]] void *env, void *pointer, [[maybe_unused]] void* hint)
35 {
36     if (pointer == nullptr) {
37         return;
38     }
39     auto icuListFormat = reinterpret_cast<icu::ListFormatter *>(pointer);
40     icuListFormat->~ListFormatter();
41     delete icuListFormat;
42 }
43 
SetIcuListFormatter(JSThread * thread,const JSHandle<JSListFormat> listFormat,icu::ListFormatter * icuListFormatter,const NativePointerCallback & callback)44 void JSListFormat::SetIcuListFormatter(JSThread *thread, const JSHandle<JSListFormat> listFormat,
45                                        icu::ListFormatter *icuListFormatter, const NativePointerCallback &callback)
46 {
47     EcmaVM *ecmaVm = thread->GetEcmaVM();
48     ObjectFactory *factory = ecmaVm->GetFactory();
49     ASSERT(icuListFormatter != nullptr);
50     JSTaggedValue data = listFormat->GetIcuLF();
51     if (data.IsJSNativePointer()) {
52         JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
53         native->ResetExternalPointer(thread, icuListFormatter);
54         return;
55     }
56     JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuListFormatter, callback);
57     listFormat->SetIcuLF(thread, pointer.GetTaggedValue());
58 }
59 
GetAvailableLocales(JSThread * thread)60 JSHandle<TaggedArray> JSListFormat::GetAvailableLocales(JSThread *thread)
61 {
62     JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
63     JSHandle<JSTaggedValue> listFormatLocales = env->GetListFormatLocales();
64     if (!listFormatLocales->IsUndefined()) {
65         return JSHandle<TaggedArray>::Cast(listFormatLocales);
66     }
67     const char *key = "listPattern";
68     const char *path = nullptr;
69     std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path);
70     JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
71     env->SetListFormatLocales(thread, availableLocales);
72     return availableLocales;
73 }
74 
75 // 13. InitializeListFormat ( listformat, locales, options )
InitializeListFormat(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSTaggedValue> & locales,const JSHandle<JSTaggedValue> & options)76 JSHandle<JSListFormat> JSListFormat::InitializeListFormat(JSThread *thread,
77                                                           const JSHandle<JSListFormat> &listFormat,
78                                                           const JSHandle<JSTaggedValue> &locales,
79                                                           const JSHandle<JSTaggedValue> &options)
80 {
81     [[maybe_unused]] EcmaHandleScope scope(thread);
82     EcmaVM *ecmaVm = thread->GetEcmaVM();
83     ObjectFactory *factory = ecmaVm->GetFactory();
84     auto globalConst = thread->GlobalConstants();
85 
86     // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
87     JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
88     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
89 
90     // 4. Let options be ? GetOptionsObject(options).
91     JSHandle<JSObject> optionsObject;
92     if (options->IsUndefined()) {
93         optionsObject = factory->CreateNullJSObject();
94     } else if (!options->IsJSObject()) {
95         THROW_TYPE_ERROR_AND_RETURN(thread, "options is not Object", listFormat);
96     } else {
97         optionsObject = JSTaggedValue::ToObject(thread, options);
98         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
99     }
100 
101     // 5. Let opt be a new Record.
102     // 6. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
103     JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
104     auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
105         thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
106         {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
107     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
108 
109     // 8. Let localeData be %ListFormat%.[[LocaleData]].
110     JSHandle<TaggedArray> availableLocales;
111     if (requestedLocales->GetLength() == 0) {
112         availableLocales = factory->EmptyArray();
113     } else {
114         availableLocales = GetAvailableLocales(thread);
115     }
116 
117     // 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales,
118     // opt, %ListFormat%.[[RelevantExtensionKeys]], localeData).
119     std::set<std::string> relevantExtensionKeys {""};
120     ResolvedLocale r =
121         JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
122     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
123 
124     // 10. Set listFormat.[[Locale]] to r.[[locale]].
125     icu::Locale icuLocale = r.localeData;
126     JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
127     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
128     listFormat->SetLocale(thread, localeStr.GetTaggedValue());
129 
130     // 11. Let type be ? GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction").
131     property = globalConst->GetHandledTypeString();
132     auto type = JSLocale::GetOptionOfString<ListTypeOption>(thread, optionsObject, property,
133                                                             {ListTypeOption::CONJUNCTION, ListTypeOption::DISJUNCTION,
134                                                             ListTypeOption::UNIT},
135                                                             {"conjunction", "disjunction", "unit"},
136                                                             ListTypeOption::CONJUNCTION);
137     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
138 
139     // 12. Set listFormat.[[Type]] to type.
140     listFormat->SetType(type);
141 
142     // 13. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
143     property = globalConst->GetHandledStyleString();
144     auto style = JSLocale::GetOptionOfString<ListStyleOption>(thread, optionsObject, property,
145                                                               {ListStyleOption::LONG, ListStyleOption::SHORT,
146                                                               ListStyleOption::NARROW},
147                                                               {"long", "short", "narrow"}, ListStyleOption::LONG);
148     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
149 
150     // 14. Set listFormat.[[Style]] to style.
151     listFormat->SetStyle(style);
152 
153     // 15. Let dataLocale be r.[[dataLocale]].
154     // 16. Let dataLocaleData be localeData.[[<dataLocale>]].
155     // 17. Let dataLocaleTypes be dataLocaleData.[[<type>]].
156     // 18. Set listFormat.[[Templates]] to dataLocaleTypes.[[<style>]].
157     // 19. Return listFormat.
158 
159     // Trans typeOption to ICU type
160     UListFormatterType uType;
161     switch (type) {
162         case ListTypeOption::CONJUNCTION:
163             uType = ULISTFMT_TYPE_AND;
164             break;
165         case ListTypeOption::DISJUNCTION:
166             uType = ULISTFMT_TYPE_OR;
167             break;
168         case ListTypeOption::UNIT:
169             uType = ULISTFMT_TYPE_UNITS;
170             break;
171         default:
172             LOG_ECMA(FATAL) << "this branch is unreachable";
173             UNREACHABLE();
174     }
175 
176     // Trans StyleOption to ICU Style
177     UListFormatterWidth uStyle;
178     switch (style) {
179         case ListStyleOption::LONG:
180             uStyle = ULISTFMT_WIDTH_WIDE;
181             break;
182         case ListStyleOption::SHORT:
183             uStyle = ULISTFMT_WIDTH_SHORT;
184             break;
185         case ListStyleOption::NARROW:
186             uStyle = ULISTFMT_WIDTH_NARROW;
187             break;
188         default:
189             LOG_ECMA(FATAL) << "this branch is unreachable";
190             UNREACHABLE();
191     }
192     UErrorCode status = U_ZERO_ERROR;
193     icu::ListFormatter *icuListFormatter = icu::ListFormatter::createInstance(icuLocale, uType, uStyle, status);
194     if (U_FAILURE(status) || icuListFormatter == nullptr) {
195         delete icuListFormatter;
196         if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) {
197             THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", listFormat);
198         }
199         THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::ListFormatter failed", listFormat);
200     }
201     SetIcuListFormatter(thread, listFormat, icuListFormatter, JSListFormat::FreeIcuListFormatter);
202     return listFormat;
203 }
204 
205 // 13.1.5 StringListFromIterable ( iterable )
StringListFromIterable(JSThread * thread,const JSHandle<JSTaggedValue> & iterable)206 JSHandle<JSTaggedValue> JSListFormat::StringListFromIterable(JSThread *thread, const JSHandle<JSTaggedValue> &iterable)
207 {
208     JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
209     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
210     JSHandle<JSTaggedValue> arrayList = JSHandle<JSTaggedValue>::Cast(array);
211     // 1. If iterable is undefined, then
212     // a. Return a new empty List.
213     if (iterable->IsUndefined()) {
214         return arrayList;
215     }
216     // 2. Let iteratorRecord be ? GetIterator(iterable).
217     JSHandle<JSTaggedValue> iteratorRecord(JSIterator::GetIterator(thread, iterable));
218     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
219     // 3. Let list be a new empty List.
220     // 4. Let next be true.
221     JSHandle<JSTaggedValue> next(thread, JSTaggedValue::True());
222     // 5. Repeat, while next is not false,
223     // a. Set next to ? IteratorStep(iteratorRecord).
224     // b. If next is not false, then
225     // i. Let nextValue be ? IteratorValue(next).
226     // ii. If Type(nextValue) is not String, then
227     // 1. Let error be ThrowCompletion(a newly created TypeError object).
228     // 2. Return ? IteratorClose(iteratorRecord, error).
229     // iii. Append nextValue to the end of the List list.
230     uint32_t k = 0;
231     while (!next->IsFalse()) {
232         next = JSIterator::IteratorStep(thread, iteratorRecord);
233         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
234         if (!next->IsFalse()) {
235             JSHandle<JSTaggedValue> nextValue(JSIterator::IteratorValue(thread, next));
236             RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
237             if (!nextValue->IsString()) {
238                 ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
239                 JSHandle<JSObject> typeError =
240                     factory->GetJSError(ErrorType::TYPE_ERROR, "nextValue is not string", StackCheck::NO);
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 = intl::LocaleHelper::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 = intl::LocaleHelper::UStringToString(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 = intl::LocaleHelper::UStringToString(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                 LOG_ECMA(FATAL) << "this branch is unreachable";
329                 UNREACHABLE();
330         }
331         return result;
332     }
333 
ListOptionTypeToEcmaString(JSThread * thread,ListTypeOption type)334     JSHandle<JSTaggedValue> ListOptionTypeToEcmaString(JSThread *thread, ListTypeOption type)
335     {
336         JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
337         auto globalConst = thread->GlobalConstants();
338         switch (type) {
339             case ListTypeOption::CONJUNCTION:
340                 result.Update(globalConst->GetHandledConjunctionString().GetTaggedValue());
341                 break;
342             case ListTypeOption::DISJUNCTION:
343                 result.Update(globalConst->GetHandledDisjunctionString().GetTaggedValue());
344                 break;
345             case ListTypeOption::UNIT:
346                 result.Update(globalConst->GetHandledUnitString().GetTaggedValue());
347                 break;
348             default:
349                 LOG_ECMA(FATAL) << "this branch is unreachable";
350                 UNREACHABLE();
351         }
352         return result;
353     }
354 }
355 
356 // 13.1.3 FormatList ( listFormat, list )
FormatList(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSArray> & listArray)357 JSHandle<EcmaString> JSListFormat::FormatList(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
358                                               const JSHandle<JSArray> &listArray)
359 {
360     JSHandle<EcmaString> stringValue;
361     UErrorCode status = U_ZERO_ERROR;
362     icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray);
363     if (U_FAILURE(status)) {
364         THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", stringValue);
365     }
366     icu::UnicodeString result = formatted.toString(status);
367     if (U_FAILURE(status)) {
368         THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", stringValue);
369     }
370     stringValue = intl::LocaleHelper::UStringToString(thread, result);
371     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, stringValue);
372     // 4. Return result
373     return stringValue;
374 }
375 
376 // 13.1.4 FormatListToParts ( listFormat, list )
FormatListToParts(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSArray> & listArray)377 JSHandle<JSArray> JSListFormat::FormatListToParts(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
378                                                   const JSHandle<JSArray> &listArray)
379 {
380     UErrorCode status = U_ZERO_ERROR;
381     icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray);
382     if (U_FAILURE(status)) {
383         THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", listArray);
384     }
385     icu::UnicodeString result = formatted.toString(status);
386     if (U_FAILURE(status)) {
387         THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", listArray);
388     }
389     JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
390     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
391     FormatListToArray(thread, formatted, array, status, result);
392     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
393     return array;
394 }
395 
ResolvedOptions(JSThread * thread,const JSHandle<JSListFormat> & listFormat,const JSHandle<JSObject> & options)396 void JSListFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
397                                    const JSHandle<JSObject> &options)
398 {
399     auto globalConst = thread->GlobalConstants();
400 
401     // [[Locale]]
402     JSHandle<JSTaggedValue> propertyKey = globalConst->GetHandledLocaleString();
403     JSHandle<JSTaggedValue> locale(thread, listFormat->GetLocale());
404     JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, locale);
405     RETURN_IF_ABRUPT_COMPLETION(thread);
406 
407     // [[type]]
408     ListTypeOption type = listFormat->GetType();
409     propertyKey = globalConst->GetHandledTypeString();
410     JSHandle<JSTaggedValue> typeString = ListOptionTypeToEcmaString(thread, type);
411     JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, typeString);
412     RETURN_IF_ABRUPT_COMPLETION(thread);
413 
414     // [[Style]]
415     ListStyleOption style = listFormat->GetStyle();
416     propertyKey = globalConst->GetHandledStyleString();
417     JSHandle<JSTaggedValue> styleString = ListOptionStyleToEcmaString(thread, style);
418     JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, styleString);
419     RETURN_IF_ABRUPT_COMPLETION(thread);
420 }
421 }  // namespace panda::ecmascript
422