1 /*
2  * Copyright (c) 2021 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_locale.h"
17 
18 #include "ecmascript/intl/locale_helper.h"
19 #include "ecmascript/object_factory-inl.h"
20 #include "ecmascript/checkpoint/thread_state_transition.h"
21 
22 #if defined(__clang__)
23 #pragma clang diagnostic push
24 #pragma clang diagnostic ignored "-Wshadow"
25 #elif defined(__GNUC__)
26 #pragma GCC diagnostic push
27 #pragma GCC diagnostic ignored "-Wshadow"
28 #endif
29 #include "unicode/localebuilder.h"
30 #if defined(__clang__)
31 #pragma clang diagnostic pop
32 #elif defined(__GNUC__)
33 #pragma GCC diagnostic pop
34 #endif
35 
36 namespace panda::ecmascript {
37 const std::string JSLocale::LATN_STRING = "latn";
38 
39 const std::vector<LocaleMatcherOption> JSLocale::LOCALE_MATCHER_OPTION = {
40     LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT
41 };
42 const std::vector<std::string> JSLocale::LOCALE_MATCHER_OPTION_NAME = {
43     "lookup", "best fit"
44 };
45 
46 const std::map<std::string, std::set<std::string>> JSLocale::LOCALE_MAP = {
47     {"hc", {"h11", "h12", "h23", "h24"}},
48     {"lb", {"strict", "normal", "loose"}},
49     {"kn", {"true", "false"}},
50     {"kf", {"upper", "lower", "false"}}
51 };
52 
53 const std::vector<std::string> JSLocale::HOUR_CYCLE = {"h11", "h12", "h23", "h24"};
54 const std::vector<std::string> JSLocale::CASE_FIRST = {"upper", "lower", "false"};
55 
56 const std::set<std::string> JSLocale::WELL_NUMBER_SYSTEM = {"native", "traditio", "finance"};
57 const std::set<std::string> JSLocale::WELL_COLLATION = {"standard", "search"};
58 // 6.4.1 IsValidTimeZoneName ( timeZone )
IsValidTimeZoneName(const icu::TimeZone &tz)59 bool JSLocale::IsValidTimeZoneName(const icu::TimeZone &tz)
60 {
61     UErrorCode status = U_ZERO_ERROR;
62     icu::UnicodeString id;
63     tz.getID(id);
64     icu::UnicodeString canonical;
65     icu::TimeZone::getCanonicalID(id, canonical, status);
66     UBool canonicalFlag = (canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV));
67     return (U_SUCCESS(status) != 0) && (canonicalFlag != 0);
68 }
69 
70 // 9.2.3 LookupMatcher ( availableLocales, requestedLocales )
LookupMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)71 JSHandle<EcmaString> JSLocale::LookupMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
72                                              const JSHandle<TaggedArray> &requestedLocales)
73 {
74     MatcherResult result = {std::string(), std::string()};
75     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
76     // 1. Let result be a new Record.
77     // 2. For each element locale of requestedLocales in List order, do
78     std::vector<std::string> availableStringLocales = GetAvailableStringLocales(thread, availableLocales);
79     uint32_t length = requestedLocales->GetLength();
80     JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
81     for (uint32_t i = 0; i < length; ++i) {
82         locale.Update(requestedLocales->Get(thread, i));
83         // 2. a. Let noExtensionsLocale be the String value that is locale
84         //       with all Unicode locale extension sequences removed.
85         intl::LocaleHelper::ParsedLocale parsedResult = intl::LocaleHelper::HandleLocale(locale);
86         // 2. b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
87         std::string availableLocale =
88             intl::LocaleHelper::BestAvailableLocale(availableStringLocales, parsedResult.base);
89         // 2. c. If availableLocale is not undefined, append locale to the end of subset.
90         if (!availableLocale.empty()) {
91             result = {std::string(), std::string()};
92             // 2. c. i. Set result.[[locale]] to availableLocale.
93             result.locale = availableLocale;
94             // 2. c. ii. If locale and noExtensionsLocale are not the same String value, then
95             // 2. c. ii. 1. Let extension be the String value consisting of  the first substring of locale that is a
96             //              Unicode locale extension sequence.
97             if (!parsedResult.extension.empty()) {
98                 result.extension = parsedResult.extension;
99             }
100             // 2. c. ii. 2. Set result.[[extension]] to extension.
101             std::string res = result.locale + result.extension;
102             // 2. c. iii. Return result.
103             return factory->NewFromStdString(res);
104         }
105     }
106 
107     // 3. Let defLocale be DefaultLocale();
108     // 4. Set result.[[locale]] to defLocale.
109     // 5. Return result.
110     auto defLocale = intl::LocaleHelper::StdStringDefaultLocale(thread);
111     result.locale = defLocale;
112     return factory->NewFromStdString(result.locale);
113 }
114 
BuildLocaleMatcher(JSThread *thread, uint32_t *availableLength, UErrorCode *status, const JSHandle<TaggedArray> &availableLocales)115 icu::LocaleMatcher BuildLocaleMatcher(JSThread *thread, uint32_t *availableLength, UErrorCode *status,
116                                       const JSHandle<TaggedArray> &availableLocales)
117 {
118     std::string locale = intl::LocaleHelper::StdStringDefaultLocale(thread);
119     icu::Locale defaultLocale = icu::Locale::forLanguageTag(locale, *status);
120     ASSERT_PRINT(U_SUCCESS(*status), "icu::Locale::forLanguageTag failed");
121     icu::LocaleMatcher::Builder builder;
122     builder.setDefaultLocale(&defaultLocale);
123     uint32_t length = availableLocales->GetLength();
124 
125     JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
126     for (*availableLength = 0; *availableLength < length; ++(*availableLength)) {
127         item.Update(availableLocales->Get(thread, *availableLength));
128         std::string itemStr = intl::LocaleHelper::ConvertToStdString(item);
129         icu::Locale localeForLanguageTag = icu::Locale::forLanguageTag(itemStr, *status);
130         if (U_SUCCESS(*status) != 0) {
131             builder.addSupportedLocale(localeForLanguageTag);
132         } else {
133             break;
134         }
135     }
136     *status = U_ZERO_ERROR;
137     return builder.build(*status);
138 }
139 
140 // 9.2.4 BestFitMatcher ( availableLocales, requestedLocales )
BestFitMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)141 JSHandle<EcmaString> JSLocale::BestFitMatcher(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
142                                               const JSHandle<TaggedArray> &requestedLocales)
143 {
144     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
145     UErrorCode status = U_ZERO_ERROR;
146     uint32_t availableLength = availableLocales->GetLength();
147     icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
148     ASSERT(U_SUCCESS(status));
149 
150     uint32_t requestedLocalesLength = requestedLocales->GetLength();
151     JSIntlIterator iter(requestedLocales, requestedLocalesLength);
152     auto bestFit = matcher.getBestMatch(iter, status)->toLanguageTag<std::string>(status);
153 
154     if (U_FAILURE(status) != 0) {
155         return intl::LocaleHelper::DefaultLocale(thread);
156     }
157 
158     for (uint32_t i = 0; i < requestedLocalesLength; ++i) {
159         if (iter[i] == bestFit) {
160             return JSHandle<EcmaString>(thread, requestedLocales->Get(thread, i));
161         }
162     }
163     return factory->NewFromStdString(bestFit);
164 }
165 
166 // 9.2.8 LookupSupportedLocales ( availableLocales, requestedLocales )
LookupSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)167 JSHandle<TaggedArray> JSLocale::LookupSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
168                                                        const JSHandle<TaggedArray> &requestedLocales)
169 {
170     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
171     uint32_t length = requestedLocales->GetLength();
172     JSMutableHandle<EcmaString> item(thread, JSTaggedValue::Undefined());
173     std::vector<std::string> availableStringLocales = GetAvailableStringLocales(thread, availableLocales);
174     std::vector<std::string> convertedLocales;
175     convertedLocales.reserve(length);
176     std::vector<uint32_t> indexAvailableLocales;
177     indexAvailableLocales.reserve(length);
178     for (uint32_t i = 0; i < length; ++i) {
179         item.Update(requestedLocales->Get(thread, i));
180         convertedLocales.push_back(intl::LocaleHelper::ConvertToStdString(item));
181     }
182     // 1. For each element locale of requestedLocales in List order, do
183     //    a. Let noExtensionsLocale be the String value that is locale with all Unicode locale extension sequences
184     //       removed.
185     //    b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
186     //    c. If availableLocale is not undefined, append locale to the end of subset.
187     {
188         ThreadNativeScope nativeScope(thread);
189         for (uint32_t i = 0; i < length; ++i) {
190             intl::LocaleHelper::ParsedLocale foundationResult = intl::LocaleHelper::HandleLocale(convertedLocales[i]);
191             std::string availableLocale =
192                 intl::LocaleHelper::BestAvailableLocale(availableStringLocales, foundationResult.base);
193             if (!availableLocale.empty()) {
194                 indexAvailableLocales.push_back(i);
195             }
196         }
197     }
198     // 2. Let subset be a new empty List.
199     JSHandle<TaggedArray> subset = factory->NewTaggedArray(indexAvailableLocales.size());
200     uint32_t index = 0;
201     for (uint32_t i = 0; i < indexAvailableLocales.size(); ++i) {
202         subset->Set(thread, index++, requestedLocales->Get(thread, indexAvailableLocales[i]));
203     }
204     // 3. Return subset.
205     return subset;
206 }
207 
208 // 9.2.9 BestFitSupportedLocales ( availableLocales, requestedLocales )
BestFitSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales)209 JSHandle<TaggedArray> JSLocale::BestFitSupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
210                                                         const JSHandle<TaggedArray> &requestedLocales)
211 {
212     UErrorCode status = U_ZERO_ERROR;
213     uint32_t requestLength = requestedLocales->GetLength();
214     uint32_t availableLength = availableLocales->GetLength();
215     icu::LocaleMatcher matcher = BuildLocaleMatcher(thread, &availableLength, &status, availableLocales);
216     ASSERT(U_SUCCESS(status));
217 
218     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
219     JSHandle<EcmaString> defaultLocale = intl::LocaleHelper::DefaultLocale(thread);
220     JSHandle<TaggedArray> result = factory->NewTaggedArray(requestLength);
221 
222     uint32_t index = 0;
223     JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
224     for (uint32_t i = 0; i < requestLength; ++i) {
225         locale.Update(requestedLocales->Get(thread, i));
226         if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), locale, defaultLocale)) {
227             result->Set(thread, index++, locale.GetTaggedValue());
228         } else {
229             status = U_ZERO_ERROR;
230             std::string localeStr = intl::LocaleHelper::ConvertToStdString(locale);
231             icu::Locale desired = icu::Locale::forLanguageTag(localeStr, status);
232             auto bestFit = matcher.getBestMatch(desired, status)->toLanguageTag<std::string>(status);
233             if ((U_SUCCESS(status) != 0) &&
234                 EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), locale, factory->NewFromStdString(bestFit))) {
235                 result->Set(thread, index++, locale.GetTaggedValue());
236             }
237         }
238     }
239     result = TaggedArray::SetCapacity(thread, result, index);
240     return result;
241 }
242 
243 // 9.2.10 SupportedLocales ( availableLocales, requestedLocales, options )
SupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales, const JSHandle<JSTaggedValue> &options)244 JSHandle<JSArray> JSLocale::SupportedLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
245                                              const JSHandle<TaggedArray> &requestedLocales,
246                                              const JSHandle<JSTaggedValue> &options)
247 {
248     const GlobalEnvConstants *globalConst = thread->GlobalConstants();
249     // 1. If options is not undefined, then
250     //    a. Let options be ? ToObject(options).
251     //    b. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
252     // 2. Else, let matcher be "best fit".
253     if (!options->IsUndefined()) {
254         JSHandle<JSObject> obj = JSTaggedValue::ToObject(thread, options);
255         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
256 
257         [[maybe_unused]] LocaleMatcherOption matcher = GetOptionOfString<LocaleMatcherOption>(thread,
258             obj, globalConst->GetHandledLocaleMatcherString(),
259             JSLocale::LOCALE_MATCHER_OPTION, JSLocale::LOCALE_MATCHER_OPTION_NAME,
260             LocaleMatcherOption::BEST_FIT);
261         RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
262     }
263 
264     // 3. If matcher is "best fit", then
265     //    a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
266     // 4. Else,
267     //    a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
268     JSMutableHandle<TaggedArray> supportedLocales(thread, JSTaggedValue::Undefined());
269     supportedLocales.Update(LookupSupportedLocales(thread, availableLocales, requestedLocales).GetTaggedValue());
270 
271     JSHandle<JSArray> subset = JSArray::CreateArrayFromList(thread, supportedLocales);
272     // 5. Return CreateArrayFromList(supportedLocales).
273     return subset;
274 }
275 
276 // 9.2.11 GetOption ( options, property, type, values, fallback )
GetOption(JSThread *thread, const JSHandle<JSObject> &options, const JSHandle<JSTaggedValue> &property, OptionType type, const JSHandle<JSTaggedValue> &values, const JSHandle<JSTaggedValue> &fallback)277 JSHandle<JSTaggedValue> JSLocale::GetOption(JSThread *thread, const JSHandle<JSObject> &options,
278                                             const JSHandle<JSTaggedValue> &property, OptionType type,
279                                             const JSHandle<JSTaggedValue> &values,
280                                             const JSHandle<JSTaggedValue> &fallback)
281 {
282     // 1. Let value be ? Get(options, property).
283     JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
284     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
285 
286     // 2. If value is not undefined, then
287     if (!value->IsUndefined()) {
288         // a. Assert: type is "boolean" or "string".
289         ASSERT_PRINT(type == OptionType::BOOLEAN || type == OptionType::STRING, "type is not boolean or string");
290 
291         // b. If type is "boolean", then
292         //    i. Let value be ToBoolean(value).
293         if (type == OptionType::BOOLEAN) {
294             value = JSHandle<JSTaggedValue>(thread, JSTaggedValue(value->ToBoolean()));
295         }
296         // c. If type is "string", then
297         //    i. Let value be ? ToString(value).
298         if (type == OptionType::STRING) {
299             JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, value);
300             RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
301             value = JSHandle<JSTaggedValue>(thread, str.GetTaggedValue());
302         }
303 
304         // d. If values is not undefined, then
305         //    i. If values does not contain an element equal to value, throw a RangeError exception.
306         if (!values->IsUndefined()) {
307             bool isExist = false;
308             JSHandle<TaggedArray> valuesArray = JSHandle<TaggedArray>::Cast(values);
309             uint32_t length = valuesArray->GetLength();
310             for (uint32_t i = 0; i < length; i++) {
311                 if (JSTaggedValue::SameValue(valuesArray->Get(thread, i), value.GetTaggedValue())) {
312                     isExist = true;
313                 }
314             }
315             if (!isExist) {
316                 JSHandle<JSTaggedValue> exception(thread, JSTaggedValue::Exception());
317                 THROW_RANGE_ERROR_AND_RETURN(thread, "values does not contain an element equal to value", exception);
318             }
319         }
320         // e. Return value.
321         return value;
322     }
323     // 3. Else, return fallback.
324     return fallback;
325 }
326 
GetOptionOfString(JSThread *thread, const JSHandle<JSObject> &options, const JSHandle<JSTaggedValue> &property, const std::vector<std::string> &values, std::string *optionValue)327 bool JSLocale::GetOptionOfString(JSThread *thread, const JSHandle<JSObject> &options,
328                                  const JSHandle<JSTaggedValue> &property, const std::vector<std::string> &values,
329                                  std::string *optionValue)
330 {
331     // 1. Let value be ? Get(options, property).
332     OperationResult operationResult = JSObject::GetProperty(thread, options, property);
333     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
334     JSHandle<JSTaggedValue> value = operationResult.GetValue();
335     // 2. If value is not undefined, then
336     if (value->IsUndefined()) {
337         return false;
338     }
339     //    c. If type is "string" "string", then
340     //       i. Let value be ? ToString(value).
341     JSHandle<EcmaString> valueEStr = JSTaggedValue::ToString(thread, value);
342     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
343     if (EcmaStringAccessor(valueEStr).IsUtf16()) {
344         THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
345     }
346     *optionValue = intl::LocaleHelper::ConvertToStdString(valueEStr);
347     if (values.empty()) {
348         return true;
349     }
350     // d. If values is not undefined, then
351     //    i. If values does not contain an element equal to value, throw a RangeError exception.
352     for (const auto &item : values) {
353         if (item == *optionValue) {
354             return true;
355         }
356     }
357     THROW_RANGE_ERROR_AND_RETURN(thread, "Value out of range for locale options property", false);
358 }
359 
360 // 9.2.12 DefaultNumberOption ( value, minimum, maximum, fallback )
DefaultNumberOption(JSThread *thread, const JSHandle<JSTaggedValue> &value, int minimum, int maximum, int fallback)361 int JSLocale::DefaultNumberOption(JSThread *thread, const JSHandle<JSTaggedValue> &value, int minimum, int maximum,
362                                   int fallback)
363 {
364     // 1. If value is not undefined, then
365     if (!value->IsUndefined()) {
366         // a. Let value be ? ToNumber(value).
367         JSTaggedNumber number = JSTaggedValue::ToNumber(thread, value);
368         RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
369         // b. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
370         double num = JSTaggedValue(number).GetNumber();
371         if (std::isnan(num) || num < minimum || num > maximum) {
372             THROW_RANGE_ERROR_AND_RETURN(thread, "", fallback);
373         }
374         // c. Return floor(value).
375         return std::floor(num);
376     }
377     // 2. Else, return fallback.
378     return fallback;
379 }
380 
381 // 9.2.13 GetNumberOption ( options, property, minimum, maximum, fallback )
GetNumberOption(JSThread *thread, const JSHandle<JSObject> &options, const JSHandle<JSTaggedValue> &property, int min, int max, int fallback)382 int JSLocale::GetNumberOption(JSThread *thread, const JSHandle<JSObject> &options,
383                               const JSHandle<JSTaggedValue> &property, int min, int max, int fallback)
384 {
385     // 1. Let value be ? Get(options, property).
386     JSHandle<JSTaggedValue> value = JSObject::GetProperty(thread, options, property).GetValue();
387     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
388 
389     // 2. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
390     int result = DefaultNumberOption(thread, value, min, max, fallback);
391     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, fallback);
392     return result;
393 }
394 
395 // 9.2.5 UnicodeExtensionValue ( extension, key )
UnicodeExtensionValue(const std::string extension, const std::string key)396 std::string JSLocale::UnicodeExtensionValue(const std::string extension, const std::string key)
397 {
398     // 1. Assert: The number of elements in key is 2.
399     // 2. Let size be the number of elements in extension.
400     ASSERT(key.size() == INTL_INDEX_TWO || key.size() == INTL_INDEX_ZERO);
401     size_t size = extension.size();
402     // 3. Let searchValue be the concatenation of "-" , key, and "-".
403     std::string searchValue = "-" + key + "-";
404     // 4. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
405     size_t pos = extension.find(searchValue);
406     // 5. If pos ≠ -1, then
407     if (pos != std::string::npos) {
408         // a. Let start be pos + 4.
409         size_t start = pos + INTL_INDEX_FOUR;
410         // b. Let end be start.
411         size_t end = start;
412         // c. Let k be start.
413         size_t k = start;
414         // d. Let done be false.
415         bool done = false;
416         // e. Repeat, while done is false
417         while (!done) {
418             // i. Let e be Call(%StringProto_indexOf%, extension, « "-", k »).
419             size_t e = extension.find("-", k);
420             size_t len;
421             // ii. If e = -1, let len be size - k; else let len be e - k.
422             if (e == std::string::npos) {
423                 len = size - k;
424             } else {
425                 len = e - k;
426             }
427             // iii. If len = 2, then
428             //     1. Let done be true.
429             if (len == INTL_INDEX_TWO) {
430                 done = true;
431             // iv. Else if e = -1, then
432             //    1. Let end be size.
433             //    2. Let done be true.
434             } else if (e == std::string::npos) {
435                 end = size;
436                 done = true;
437             // v. Else,
438             //   1. Let end be e.
439             //   2. Let k be e + 1.
440             } else {
441                 end = e;
442                 k = e + INTL_INDEX_ONE;
443             }
444         }
445         // f. Return the String value equal to the substring of extension consisting of the code units at indices.
446         // start (inclusive) through end (exclusive).
447         std::string result = extension.substr(start, end - start);
448         return result;
449     }
450     // 6. Let searchValue be the concatenation of "-" and key.
451     searchValue = "-" + key;
452     // 7. Let pos be Call(%StringProto_indexOf%, extension, « searchValue »).
453     pos = extension.find(searchValue);
454     // 8. If pos ≠ -1 and pos + 3 = size, then
455     //    a. Return the empty String.
456     if (pos != std::string::npos && pos + INTL_INDEX_THREE == size) {
457         return "";
458     }
459     // 9. Return undefined.
460     return "undefined";
461 }
462 
ResolveLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales, const JSHandle<TaggedArray> &requestedLocales, [[maybe_unused]] LocaleMatcherOption matcher, const std::set<std::string> &relevantExtensionKeys)463 ResolvedLocale JSLocale::ResolveLocale(JSThread *thread, const JSHandle<TaggedArray> &availableLocales,
464                                        const JSHandle<TaggedArray> &requestedLocales,
465                                        [[maybe_unused]] LocaleMatcherOption matcher,
466                                        const std::set<std::string> &relevantExtensionKeys)
467 {
468     // 1. Let matcher be options.[[localeMatcher]].
469     // 2. If matcher is "lookup" "lookup", then
470     //    a. Let r be LookupMatcher(availableLocales, requestedLocales).
471     // 3. Else,
472     //    a. Let r be BestFitMatcher(availableLocales, requestedLocales).
473     JSMutableHandle<EcmaString> locale(thread, JSTaggedValue::Undefined());
474     if (availableLocales->GetLength() == 0 && requestedLocales->GetLength() == 0) {
475         locale.Update(intl::LocaleHelper::DefaultLocale(thread).GetTaggedValue());
476     } else {
477         locale.Update(LookupMatcher(thread, availableLocales, requestedLocales).GetTaggedValue());
478     }
479 
480     // 4. Let foundLocale be r.[[locale]].
481     // 5. Let result be a new Record.
482     // 6. Set result.[[dataLocale]] to foundLocale.
483     // 7. Let supportedExtension be "-u".
484     std::string foundLocale = intl::LocaleHelper::ConvertToStdString(locale);
485     icu::Locale foundLocaleData = BuildICULocale(foundLocale);
486     ResolvedLocale result;
487     result.localeData = foundLocaleData;
488     JSHandle<EcmaString> tag = intl::LocaleHelper::ToLanguageTag(thread, foundLocaleData);
489     result.locale = intl::LocaleHelper::ConvertToStdString(tag);
490     std::string supportedExtension = "-u";
491     icu::LocaleBuilder localeBuilder;
492     localeBuilder.setLocale(foundLocaleData).clearExtensions();
493     // 8. For each element key of relevantExtensionKeys in List order, do
494     for (auto &key : relevantExtensionKeys) {
495         auto doubleMatch = foundLocale.find(key);
496         if (doubleMatch == std::string::npos) {
497             continue;
498         }
499         UErrorCode status = U_ZERO_ERROR;
500         std::set<std::string> keyLocaleData;
501         std::unique_ptr<icu::StringEnumeration> wellFormKey(foundLocaleData.createKeywords(status));
502         if (U_FAILURE(status) != 0) {
503             return result;
504         }
505         if (!wellFormKey) {
506             return result;
507         }
508         std::string value;
509 
510         // c. Let keyLocaleData be foundLocaleData.[[<key>]].
511         // e. Let value be keyLocaleData[0].
512         if ((key != "ca") && (key != "co") && (key != "nu")) {
513             auto find = JSLocale::LOCALE_MAP.find(key);
514             if (find != JSLocale::LOCALE_MAP.end()) {
515                 keyLocaleData = find->second;
516             }
517             if (key == "") {
518                 keyLocaleData = JSLocale::LOCALE_MAP.at("lb");
519             }
520             value = *keyLocaleData.begin();
521         }
522 
523         // g. Let supportedExtensionAddition be "".
524         // h. If r has an [[extension]] field, then
525         std::string supportedExtensionAddition;
526         size_t found = foundLocale.find("-u-");
527         if (found != std::string::npos) {
528             std::string extension = foundLocale.substr(found + INTL_INDEX_ONE);
529 
530             // i. Let requestedValue be UnicodeExtensionValue(r.[[extension]], key).
531             std::string requestedValue = UnicodeExtensionValue(extension, key);
532             if (key == "kn" && requestedValue.empty()) {
533                 requestedValue = "true";
534             }
535 
536             // ii. If requestedValue is not undefined, then
537             if (requestedValue != "undefined") {
538                 // 1. If requestedValue is not the empty String, then
539                 if (!requestedValue.empty()) {
540                     // a. If keyLocaleData contains requestedValue, then
541                     //    i. Let value be requestedValue.
542                     //    ii. Let supportedExtensionAddition be the concatenation of "-", key, "-", and value.
543                     if (key == "ca" || key == "co") {
544                         if (key == "co") {
545                             bool isValidValue = IsWellCollation(foundLocaleData, requestedValue);
546                             if (!isValidValue) {
547                                 continue;
548                             }
549                             value = requestedValue;
550                             supportedExtensionAddition = "-" + key + "-" + value;
551                             localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
552                         } else {
553                             bool isValidValue = IsWellCalendar(foundLocaleData, requestedValue);
554                             if (!isValidValue) {
555                                 continue;
556                             }
557                             value = requestedValue;
558                             supportedExtensionAddition = "-" + key + "-" + value;
559                             localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
560                         }
561                     } else if (key == "nu") {
562                         bool isValidValue = IsWellNumberingSystem(requestedValue);
563                         if (!isValidValue) {
564                             continue;
565                         }
566                         value = requestedValue;
567                         supportedExtensionAddition = "-" + key + "-" + value;
568                         localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
569                     } else if (keyLocaleData.find(requestedValue) != keyLocaleData.end()) {
570                         value = requestedValue;
571                         supportedExtensionAddition = "-" + key + "-" + value;
572                         localeBuilder.setUnicodeLocaleKeyword(key, requestedValue);
573                     }
574                 }
575             }
576         }
577         result.extensions.emplace(key, value);
578         supportedExtension += supportedExtensionAddition;
579     }
580     size_t found = foundLocale.find("-u-");
581     if (found != std::string::npos) {
582         foundLocale = foundLocale.substr(0, found);
583     }
584 
585     // 9. If the number of elements in supportedExtension is greater than 2, then
586     if (supportedExtension.size() > 2) {
587         // a. Let privateIndex be Call(%StringProto_indexOf%, foundLocale, « "-x-" »).
588         size_t privateIndex = foundLocale.find("-x-");
589         // b. If privateIndex = -1, then
590         //    i. Let foundLocale be the concatenation of foundLocale and supportedExtension.
591         if (privateIndex == std::string::npos) {
592             foundLocale = foundLocale + supportedExtension;
593         } else {
594             std::string preExtension = foundLocale.substr(0, privateIndex);
595             std::string postExtension = foundLocale.substr(privateIndex);
596             foundLocale = preExtension + supportedExtension + postExtension;
597         }
598 
599         tag = intl::LocaleHelper::ToLanguageTag(thread, foundLocaleData);
600         if (!intl::LocaleHelper::IsStructurallyValidLanguageTag(tag)) {
601             result.extensions.erase(result.extensions.begin(), result.extensions.end());
602             result.locale = foundLocale;
603         }
604         tag = intl::LocaleHelper::CanonicalizeUnicodeLocaleId(thread, tag);
605         foundLocale = intl::LocaleHelper::ConvertToStdString(tag);
606     }
607 
608     // 10. Set result.[[locale]] to foundLocale.
609     result.locale = foundLocale;
610     UErrorCode status = U_ZERO_ERROR;
611     foundLocaleData = localeBuilder.build(status);
612     result.localeData = foundLocaleData;
613 
614     // 11. Return result.
615     return result;
616 }
617 
BuildICULocale(const std::string &bcp47Locale)618 icu::Locale JSLocale::BuildICULocale(const std::string &bcp47Locale)
619 {
620     UErrorCode status = U_ZERO_ERROR;
621     icu::Locale icuLocale = icu::Locale::forLanguageTag(bcp47Locale, status);
622     return icuLocale;
623 }
624 
ConstructLocaleList(JSThread *thread, const std::vector<std::string> &icuAvailableLocales)625 JSHandle<TaggedArray> JSLocale::ConstructLocaleList(JSThread *thread,
626                                                     const std::vector<std::string> &icuAvailableLocales)
627 {
628     EcmaVM *ecmaVm = thread->GetEcmaVM();
629     ObjectFactory *factory = ecmaVm->GetFactory();
630     JSHandle<TaggedArray> locales = factory->NewTaggedArray(icuAvailableLocales.size());
631     int32_t index = 0;
632     for (const std::string &locale : icuAvailableLocales) {
633         JSHandle<EcmaString> localeStr = factory->NewFromStdString(locale);
634         locales->Set(thread, index++, localeStr);
635     }
636     return locales;
637 }
638 
GetNumberingSystem(const icu::Locale &icuLocale)639 std::string JSLocale::GetNumberingSystem(const icu::Locale &icuLocale)
640 {
641     UErrorCode status = U_ZERO_ERROR;
642     std::unique_ptr<icu::NumberingSystem> numberingSystem(icu::NumberingSystem::createInstance(icuLocale, status));
643     if (U_SUCCESS(status) != 0) {
644         return numberingSystem->getName();
645     }
646     return JSLocale::LATN_STRING;
647 }
648 
IsWellFormedCurrencyCode(const std::string &currency)649 bool JSLocale::IsWellFormedCurrencyCode(const std::string &currency)
650 {
651     if (currency.length() != INTL_INDEX_THREE) {
652         return false;
653     }
654     return (IsAToZ(currency[INTL_INDEX_ZERO]) && IsAToZ(currency[INTL_INDEX_ONE]) && IsAToZ(currency[INTL_INDEX_TWO]));
655 }
656 
IsWellFormedCalendarCode(const std::string& calendar)657 bool JSLocale::IsWellFormedCalendarCode(const std::string& calendar)
658 {
659     std::string value = calendar;
660     while (true) {
661         std::size_t found_dash = value.find('-');
662         if (found_dash == std::string::npos) {
663             return IsAlphanum(value, INTL_INDEX_THREE, INTL_INDEX_EIGHT);
664         }
665         if (!IsAlphanum(value.substr(0, found_dash), INTL_INDEX_THREE, INTL_INDEX_EIGHT)) {
666             return false;
667         }
668         value = value.substr(found_dash + 1);
669     }
670 }
671 
PutElement(JSThread *thread, int index, const JSHandle<JSArray> &array, const JSHandle<JSTaggedValue> &fieldTypeString, const JSHandle<JSTaggedValue> &value)672 JSHandle<JSObject> JSLocale::PutElement(JSThread *thread, int index, const JSHandle<JSArray> &array,
673                                         const JSHandle<JSTaggedValue> &fieldTypeString,
674                                         const JSHandle<JSTaggedValue> &value)
675 {
676     auto ecmaVm = thread->GetEcmaVM();
677     ObjectFactory *factory = ecmaVm->GetFactory();
678 
679     // Let record be ! ObjectCreate(%ObjectPrototype%).
680     JSHandle<JSObject> record = factory->NewEmptyJSObject();
681 
682     auto globalConst = thread->GlobalConstants();
683     // obj.type = field_type_string
684     JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledTypeString(), fieldTypeString);
685     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread);
686     // obj.value = value
687     JSObject::CreateDataPropertyOrThrow(thread, record, globalConst->GetHandledValueString(), value);
688     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread);
689 
690     JSTaggedValue::SetProperty(thread, JSHandle<JSTaggedValue>::Cast(array), index,
691                                JSHandle<JSTaggedValue>::Cast(record), true);
692     RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSObject, thread);
693     return record;
694 }
695 
696 // 9.2.11 GetOption ( options, property, type, values, fallback )
GetOptionOfBool(JSThread *thread, const JSHandle<JSObject> &options, const JSHandle<JSTaggedValue> &property, bool fallback, bool *res)697 bool JSLocale::GetOptionOfBool(JSThread *thread, const JSHandle<JSObject> &options,
698                                const JSHandle<JSTaggedValue> &property, bool fallback, bool *res)
699 {
700     // 1. Let value be ? Get(options, property).
701     OperationResult operationResult = JSObject::GetProperty(thread, options, property);
702     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
703     JSHandle<JSTaggedValue> value = operationResult.GetValue();
704     *res = fallback;
705     // 2. If value is not undefined, then
706     if (!value->IsUndefined()) {
707         // b. Let value be ToBoolean(value).
708         *res = value->ToBoolean();
709         return true;
710     }
711     // 3. not found
712     return false;
713 }
714 
GetNumberFieldType(JSThread *thread, JSTaggedValue x, int32_t fieldId)715 JSHandle<JSTaggedValue> JSLocale::GetNumberFieldType(JSThread *thread, JSTaggedValue x, int32_t fieldId)
716 {
717     ASSERT(x.IsNumber() || x.IsBigInt());
718     double number = 0;
719     auto globalConst = thread->GlobalConstants();
720     if (static_cast<UNumberFormatFields>(fieldId) == UNUM_INTEGER_FIELD) {
721         number = x.IsBigInt() ? number : x.GetNumber();
722         if (x.IsBigInt() || std::isfinite(number)) {
723             return globalConst->GetHandledIntegerString();
724         }
725         if (std::isnan(number)) {
726             return globalConst->GetHandledNanString();
727         }
728         return globalConst->GetHandledInfinityString();
729     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_FRACTION_FIELD) {
730         return globalConst->GetHandledFractionString();
731     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_DECIMAL_SEPARATOR_FIELD) {
732         return globalConst->GetHandledDecimalString();
733     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_GROUPING_SEPARATOR_FIELD) {
734         return globalConst->GetHandledGroupString();
735     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_CURRENCY_FIELD) {
736         return globalConst->GetHandledCurrencyString();
737     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_PERCENT_FIELD) {
738         return globalConst->GetHandledPercentSignString();
739     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_SIGN_FIELD) {
740         if (x.IsBigInt()) {
741             JSHandle<JSTaggedValue> bigint(thread, x);
742             JSHandle<BigInt> value(thread, JSTaggedValue::ToBigInt(thread, bigint));
743             RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
744             return value->GetSign() ? globalConst->GetHandledMinusSignString()
745                                     : globalConst->GetHandledPlusSignString();
746         }
747         number = x.GetNumber();
748         return std::signbit(number) ? globalConst->GetHandledMinusSignString()
749                                     : globalConst->GetHandledPlusSignString();
750     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SYMBOL_FIELD) {
751         return globalConst->GetHandledExponentSeparatorString();
752     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_SIGN_FIELD) {
753         return globalConst->GetHandledExponentMinusSignString();
754     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_EXPONENT_FIELD) {
755         return globalConst->GetHandledExponentIntegerString();
756     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_COMPACT_FIELD) {
757         return globalConst->GetHandledCompactString();
758     } else if (static_cast<UNumberFormatFields>(fieldId) == UNUM_MEASURE_UNIT_FIELD) {
759         return globalConst->GetHandledUnitString();
760     } else {
761         LOG_ECMA(FATAL) << "this branch is unreachable";
762         UNREACHABLE();
763     }
764 }
765 
766 // 10.1.1 ApplyOptionsToTag( tag, options )
ApplyOptionsToTag(JSThread *thread, const JSHandle<EcmaString> &tag, const JSHandle<JSObject> &options, TagElements &tagElements)767 bool JSLocale::ApplyOptionsToTag(JSThread *thread, const JSHandle<EcmaString> &tag, const JSHandle<JSObject> &options,
768                                  TagElements &tagElements)
769 {
770     EcmaVM *ecmaVm = thread->GetEcmaVM();
771     const GlobalEnvConstants *globalConst = thread->GlobalConstants();
772     ObjectFactory *factory = ecmaVm->GetFactory();
773     if (*tag == *(factory->GetEmptyString())) {
774         return false;
775     }
776     // 2. If intl::LocaleHelper::IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
777     if (!intl::LocaleHelper::IsStructurallyValidLanguageTag(tag)) {
778         return false;
779     }
780 
781     tagElements.language =
782         GetOption(thread, options, globalConst->GetHandledLanguageString(), OptionType::STRING,
783                   globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
784     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
785 
786     // 4. If language is not undefined, then
787     //    a. If language does not match the unicode_language_subtag production, throw a RangeError exception.
788     if (!tagElements.language->IsUndefined()) {
789         std::string languageStr =
790             intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.language));
791         if (languageStr[INTL_INDEX_ZERO] == '\0' ||
792             IsAlpha(languageStr, INTL_INDEX_FOUR, INTL_INDEX_FOUR)) {
793             return false;
794         }
795     }
796 
797     // 5. Let script be ? GetOption(options, "script", "string", undefined, undefined).
798     tagElements.script =
799         GetOption(thread, options, globalConst->GetHandledScriptString(), OptionType::STRING,
800                   globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
801     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
802 
803     // 6. If script is not undefined, then
804     //    a. If script does not match the unicode_script_subtag production, throw a RangeError exception.
805     if (!tagElements.script->IsUndefined()) {
806         std::string scriptStr =
807             intl::LocaleHelper::ConvertToStdString((JSHandle<EcmaString>::Cast(tagElements.script)));
808         if (scriptStr[INTL_INDEX_ZERO] == '\0') {
809             return false;
810         }
811     }
812 
813     // 7. Let region be ? GetOption(options, "region", "string", undefined, undefined).
814     // 8. If region is not undefined, then
815     //    a. If region does not match the unicode_region_subtag production, throw a RangeError exception.
816     tagElements.region =
817         GetOption(thread, options, globalConst->GetHandledRegionString(), OptionType::STRING,
818                   globalConst->GetHandledUndefined(), globalConst->GetHandledUndefined());
819     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
820 
821     if (!tagElements.region->IsUndefined()) {
822         std::string regionStr = intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(tagElements.region));
823         if (regionStr[INTL_INDEX_ZERO] == '\0') {
824             return false;
825         }
826     }
827     return true;
828 }
829 
BuildOptionsTags(const JSHandle<EcmaString> &tag, icu::LocaleBuilder *builder, JSHandle<JSTaggedValue> language, JSHandle<JSTaggedValue> script, JSHandle<JSTaggedValue> region)830 bool BuildOptionsTags(const JSHandle<EcmaString> &tag, icu::LocaleBuilder *builder, JSHandle<JSTaggedValue> language,
831                       JSHandle<JSTaggedValue> script, JSHandle<JSTaggedValue> region)
832 {
833     std::string tagStr = intl::LocaleHelper::ConvertToStdString(tag);
834     int32_t len = static_cast<int32_t>(tagStr.length());
835     ASSERT(len > 0);
836     builder->setLanguageTag({ tagStr.c_str(), len });
837     UErrorCode status = U_ZERO_ERROR;
838     icu::Locale locale = builder->build(status);
839     locale.canonicalize(status);
840     if (U_FAILURE(status) != 0) {
841         return false;
842     }
843     builder->setLocale(locale);
844 
845     if (!language->IsUndefined()) {
846         std::string languageStr = intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(language));
847         builder->setLanguage(languageStr);
848         builder->build(status);
849         if ((U_FAILURE(status) != 0)) {
850             return false;
851         }
852     }
853 
854     if (!script->IsUndefined()) {
855         std::string scriptStr = intl::LocaleHelper::ConvertToStdString((JSHandle<EcmaString>::Cast(script)));
856         builder->setScript(scriptStr);
857         builder->build(status);
858         if ((U_FAILURE(status) != 0)) {
859             return false;
860         }
861     }
862 
863     if (!region->IsUndefined()) {
864         std::string regionStr = intl::LocaleHelper::ConvertToStdString(JSHandle<EcmaString>::Cast(region));
865         builder->setRegion(regionStr);
866         builder->build(status);
867         if ((U_FAILURE(status) != 0)) {
868             return false;
869         }
870     }
871     return true;
872 }
873 
InsertOptions(JSThread *thread, const JSHandle<JSObject> &options, icu::LocaleBuilder *builder)874 bool InsertOptions(JSThread *thread, const JSHandle<JSObject> &options, icu::LocaleBuilder *builder)
875 {
876     const std::vector<std::string> emptyValues = {};
877     const GlobalEnvConstants *globalConst = thread->GlobalConstants();
878     std::string strResult;
879     bool findca =
880         JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCalendarString(), emptyValues, &strResult);
881     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
882     if (findca) {
883         if (!uloc_toLegacyType(uloc_toLegacyKey("ca"), strResult.c_str())) {
884             return false;
885         }
886         ThreadNativeScope nativeScope(thread);
887         builder->setUnicodeLocaleKeyword("ca", strResult.c_str());
888     }
889 
890     bool findco =
891         JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCollationString(), emptyValues, &strResult);
892     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
893     if (findco) {
894         if (!uloc_toLegacyType(uloc_toLegacyKey("co"), strResult.c_str())) {
895             return false;
896         }
897         ThreadNativeScope nativeScope(thread);
898         builder->setUnicodeLocaleKeyword("co", strResult.c_str());
899     }
900 
901     bool findhc = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledHourCycleString(),
902                                               JSLocale::HOUR_CYCLE, &strResult);
903     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
904     if (findhc) {
905         if (!uloc_toLegacyType(uloc_toLegacyKey("hc"), strResult.c_str())) {
906             return false;
907         }
908         ThreadNativeScope nativeScope(thread);
909         builder->setUnicodeLocaleKeyword("hc", strResult.c_str());
910     }
911 
912     bool findkf = JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledCaseFirstString(),
913                                               JSLocale::CASE_FIRST, &strResult);
914     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
915     if (findkf) {
916         if (!uloc_toLegacyType(uloc_toLegacyKey("kf"), strResult.c_str())) {
917             return false;
918         }
919         ThreadNativeScope nativeScope(thread);
920         builder->setUnicodeLocaleKeyword("kf", strResult.c_str());
921     }
922 
923     bool boolResult = false;
924     bool findkn =
925         JSLocale::GetOptionOfBool(thread, options, globalConst->GetHandledNumericString(), false, &boolResult);
926     if (findkn) {
927         strResult = boolResult ? "true" : "false";
928         if (!uloc_toLegacyType(uloc_toLegacyKey("kn"), strResult.c_str())) {
929             return false;
930         }
931         ThreadNativeScope nativeScope(thread);
932         builder->setUnicodeLocaleKeyword("kn", strResult.c_str());
933     }
934 
935     bool findnu =
936         JSLocale::GetOptionOfString(thread, options, globalConst->GetHandledNumberingSystemString(), emptyValues,
937                                     &strResult);
938     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, false);
939     if (findnu) {
940         if (!uloc_toLegacyType(uloc_toLegacyKey("nu"), strResult.c_str())) {
941             return false;
942         }
943         ThreadNativeScope nativeScope(thread);
944         builder->setUnicodeLocaleKeyword("nu", strResult.c_str());
945     }
946     return true;
947 }
948 
InitializeLocale(JSThread *thread, const JSHandle<JSLocale> &locale, const JSHandle<EcmaString> &localeString, const JSHandle<JSObject> &options)949 JSHandle<JSLocale> JSLocale::InitializeLocale(JSThread *thread, const JSHandle<JSLocale> &locale,
950                                               const JSHandle<EcmaString> &localeString,
951                                               const JSHandle<JSObject> &options)
952 {
953     icu::LocaleBuilder builder;
954     TagElements tagElements;
955     if (!ApplyOptionsToTag(thread, localeString, options, tagElements)) {
956         THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
957     }
958 
959     bool res = BuildOptionsTags(localeString, &builder, tagElements.language, tagElements.script, tagElements.region);
960     if (!res) {
961         THROW_RANGE_ERROR_AND_RETURN(thread, "apply option to tag failed", locale);
962     }
963     bool insertResult = InsertOptions(thread, options, &builder);
964     RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, locale);
965     UErrorCode status = U_ZERO_ERROR;
966     icu::Locale icuLocale = builder.build(status);
967     icuLocale.canonicalize(status);
968 
969     if (!insertResult || (U_FAILURE(status) != 0)) {
970         THROW_RANGE_ERROR_AND_RETURN(thread, "insert or build failed", locale);
971     }
972     ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
973     factory->NewJSIntlIcuData(locale, icuLocale, JSLocale::FreeIcuLocale);
974     return locale;
975 }
976 
ConvertValue(const UErrorCode &status, std::string &value, const std::string &key)977 int ConvertValue(const UErrorCode &status, std::string &value, const std::string &key)
978 {
979     if (status == U_ILLEGAL_ARGUMENT_ERROR || value.empty()) {
980         return 1;
981     }
982 
983     if (value == "yes") {
984         value = "true";
985     }
986 
987     if (key == "kf" && value == "true") {
988         return 2;  // 2: in this case normalizedKeyword is empty string
989     }
990     return 0;
991 }
992 
NormalizeKeywordValue(JSThread *thread, const JSHandle<JSLocale> &locale, const std::string &key)993 JSTaggedValue JSLocale::NormalizeKeywordValue(JSThread *thread, const JSHandle<JSLocale> &locale,
994                                               const std::string &key)
995 {
996     icu::Locale *icuLocale = locale->GetIcuLocale();
997     UErrorCode status = U_ZERO_ERROR;
998     auto value = icuLocale->getUnicodeKeywordValue<std::string>(key, status);
999 
1000     EcmaVM *ecmaVm = thread->GetEcmaVM();
1001     ObjectFactory *factory = ecmaVm->GetFactory();
1002 
1003     int result = ConvertValue(status, value, key);
1004     if (result == 1) {
1005         return JSTaggedValue::Undefined();
1006     }
1007     if (result == 2) {  // 2: in this case normalizedKeyword is empty string
1008         return factory->GetEmptyString().GetTaggedValue();
1009     }
1010     return factory->NewFromStdString(value).GetTaggedValue();
1011 }
1012 
ToString(JSThread *thread, const JSHandle<JSLocale> &locale)1013 JSHandle<EcmaString> JSLocale::ToString(JSThread *thread, const JSHandle<JSLocale> &locale)
1014 {
1015     icu::Locale *icuLocale = locale->GetIcuLocale();
1016     if (icuLocale == nullptr) {
1017         ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
1018         return factory->GetEmptyString();
1019     }
1020     JSHandle<EcmaString> result = intl::LocaleHelper::ToLanguageTag(thread, *icuLocale);
1021     RETURN_HANDLE_IF_ABRUPT_COMPLETION(EcmaString, thread);
1022     return result;
1023 }
1024 
GetAvailableStringLocales(JSThread *thread, const JSHandle<TaggedArray> &availableLocales)1025 std::vector<std::string> JSLocale::GetAvailableStringLocales(JSThread *thread,
1026                                                              const JSHandle<TaggedArray> &availableLocales)
1027 {
1028     std::vector<std::string> availableStringLocales;
1029     JSMutableHandle<EcmaString> availableItem(thread, JSTaggedValue::Undefined());
1030     uint32_t availablecalesLength = availableLocales->GetLength();
1031     for (uint32_t i = 0; i < availablecalesLength; i++) {
1032         availableItem.Update(availableLocales->Get(thread, i));
1033         availableStringLocales.emplace_back(intl::LocaleHelper::ConvertToStdString(availableItem));
1034     }
1035     return availableStringLocales;
1036 }
1037 
1038 }  // namespace panda::ecmascript
1039