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