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