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