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/builtins/builtins_locale.h"
17
18#include "ecmascript/intl/locale_helper.h"
19#include "ecmascript/global_env.h"
20#include "ecmascript/object_factory-inl.h"
21
22namespace panda::ecmascript::builtins {
23// 10.1.3 Intl.Locale( tag [, options] )
24JSTaggedValue BuiltinsLocale::LocaleConstructor(EcmaRuntimeCallInfo *argv)
25{
26    JSThread *thread = argv->GetThread();
27    BUILTINS_API_TRACE(thread, Locale, Constructor);
28    [[maybe_unused]] EcmaHandleScope scope(thread);
29    EcmaVM *ecmaVm = thread->GetEcmaVM();
30    ObjectFactory *factory = ecmaVm->GetFactory();
31
32    // 1. If NewTarget is undefined, throw a TypeError exception.
33    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
34    if (newTarget->IsUndefined()) {
35        THROW_TYPE_ERROR_AND_RETURN(thread, "newTarget is undefined", JSTaggedValue::Exception());
36    }
37
38    // 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, %LocalePrototype%, internalSlotsList).
39    JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
40    JSHandle<JSObject> newObject = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
41    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
42    JSHandle<JSLocale> locale =JSHandle<JSLocale>::Cast(newObject);
43
44    // 7. If Type(tag) is not String or Object, throw a TypeError exception.
45    JSHandle<JSTaggedValue> tag = GetCallArg(argv, 0);
46    if (!tag->IsString() && !tag->IsJSObject()) {
47        THROW_TYPE_ERROR_AND_RETURN(thread, "tag is not String or Object", JSTaggedValue::Exception());
48    }
49
50    // 8. If Type(tag) is Object and tag has an [[InitializedLocale]] internal slot, then
51    //    a.Let tag be tag.[[Locale]].
52    // 9. Else,
53    //    a.Let tag be ? ToString(tag).
54    JSHandle<EcmaString> localeString = factory->GetEmptyString();
55    if (!tag->IsJSLocale()) {
56        localeString = JSTaggedValue::ToString(thread, tag);
57        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
58    } else {
59        icu::Locale *icuLocale = (JSHandle<JSLocale>::Cast(tag))->GetIcuLocale();
60        localeString = intl::LocaleHelper::ToLanguageTag(thread, *icuLocale);
61        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
62    }
63    // 10. If options is undefined, then
64    //    a.Let options be ! ObjectCreate(null).
65    // 11. Else
66    //    a.Let options be ? ToObject(options).
67    JSHandle<JSTaggedValue> options = GetCallArg(argv, 1);
68    JSHandle<JSObject> optionsObj;
69    if (options->IsUndefined()) {
70        optionsObj = factory->CreateNullJSObject();
71    } else {
72        optionsObj = JSTaggedValue::ToObject(thread, options);
73    }
74    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
75
76    JSHandle<JSLocale> result = JSLocale::InitializeLocale(thread, locale, localeString, optionsObj);
77    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
78    return result.GetTaggedValue();
79}
80
81JSTaggedValue BuiltinsLocale::Maximize(EcmaRuntimeCallInfo *argv)
82{
83    JSThread *thread = argv->GetThread();
84    BUILTINS_API_TRACE(thread, Locale, Maximize);
85    [[maybe_unused]] EcmaHandleScope scope(thread);
86    // 1. Let loc be the this value.
87    JSHandle<JSTaggedValue> loc = GetThis(argv);
88
89    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
90    if (!loc->IsJSLocale()) {
91        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
92    }
93    // 3. Let maximal be the result of the Add Likely Subtags algorithm applied to loc.[[Locale]]. If an error is
94    //    signaled, set maximal to loc.[[Locale]].
95    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
96    icu::Locale source(*(locale->GetIcuLocale()));
97    UErrorCode status = U_ZERO_ERROR;
98    source.addLikelySubtags(status);
99    ASSERT(U_SUCCESS(status));
100    ASSERT(!source.isBogus());
101
102    // 4. Return ! Construct(%Locale%, maximal).
103    EcmaVM *ecmaVm = thread->GetEcmaVM();
104    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
105    ObjectFactory *factory = ecmaVm->GetFactory();
106
107    JSHandle<JSFunction> ctor(env->GetLocaleFunction());
108    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(ctor);
109    factory->NewJSIntlIcuData(JSHandle<JSLocale>::Cast(obj), source, JSLocale::FreeIcuLocale);
110    return obj.GetTaggedValue();
111}
112
113JSTaggedValue BuiltinsLocale::Minimize(EcmaRuntimeCallInfo *argv)
114{
115    JSThread *thread = argv->GetThread();
116    BUILTINS_API_TRACE(thread, Locale, Minimize);
117    [[maybe_unused]] EcmaHandleScope scope(thread);
118    // 1. Let loc be the this value.
119    JSHandle<JSTaggedValue> loc = GetThis(argv);
120
121    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
122    if (!loc->IsJSLocale()) {
123        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
124    }
125
126    // 3. Let minimal be the result of the Remove Likely Subtags algorithm applied to loc.[[Locale]].
127    //    If an error is signaled, set minimal to loc.[[Locale]].
128    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
129    icu::Locale source(*(locale->GetIcuLocale()));
130    UErrorCode status = U_ZERO_ERROR;
131    source.minimizeSubtags(status);
132    ASSERT(U_SUCCESS(status));
133    ASSERT(!source.isBogus());
134
135    [[maybe_unused]] auto res = source.toLanguageTag<CString>(status);
136
137    // 4. Return ! Construct(%Locale%, minimal).
138    EcmaVM *ecmaVm = thread->GetEcmaVM();
139    JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
140    ObjectFactory *factory = ecmaVm->GetFactory();
141
142    JSHandle<JSFunction> ctor(env->GetLocaleFunction());
143    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(ctor);
144    factory->NewJSIntlIcuData(JSHandle<JSLocale>::Cast(obj), source, JSLocale::FreeIcuLocale);
145    return obj.GetTaggedValue();
146}
147
148JSTaggedValue BuiltinsLocale::ToString(EcmaRuntimeCallInfo *argv)
149{
150    JSThread *thread = argv->GetThread();
151    BUILTINS_API_TRACE(thread, Locale, ToString);
152    [[maybe_unused]] EcmaHandleScope scope(thread);
153    // 1. Let loc be the this value.
154    JSHandle<JSTaggedValue> loc = GetThis(argv);
155    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
156    if (!loc->IsJSLocale()) {
157        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
158    }
159    // 3. Return loc.[[Locale]].
160    JSHandle<EcmaString> result = JSLocale::ToString(thread, JSHandle<JSLocale>::Cast(loc));
161    return result.GetTaggedValue();
162}
163
164JSTaggedValue BuiltinsLocale::GetBaseName(EcmaRuntimeCallInfo *argv)
165{
166    JSThread *thread = argv->GetThread();
167    BUILTINS_API_TRACE(thread, Locale, GetBaseName);
168    [[maybe_unused]] EcmaHandleScope scope(thread);
169    // 1. Let loc be the this value.
170    JSHandle<JSTaggedValue> loc = GetThis(argv);
171    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
172    if (!loc->IsJSLocale()) {
173        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
174    }
175    // 3. Let locale be loc.[[Locale]].
176    // 4. Return the substring of locale corresponding to the unicode_language_id production.
177    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
178    icu::Locale icuLocale = icu::Locale::createFromName(locale->GetIcuLocale()->getBaseName());
179    JSHandle<EcmaString> baseName = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
180    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
181    return baseName.GetTaggedValue();
182}
183
184JSTaggedValue BuiltinsLocale::GetCalendar(EcmaRuntimeCallInfo *argv)
185{
186    JSThread *thread = argv->GetThread();
187    BUILTINS_API_TRACE(thread, Locale, GetCalendar);
188    [[maybe_unused]] EcmaHandleScope scope(thread);
189    // 1. Let loc be the this value.
190    JSHandle<JSTaggedValue> loc = GetThis(argv);
191    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
192    if (!loc->IsJSLocale()) {
193        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
194    }
195    // 3. Return loc.[[Calendar]].
196    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
197    JSTaggedValue calendar = JSLocale::NormalizeKeywordValue(thread, locale, "ca");
198    return calendar;
199}
200
201JSTaggedValue BuiltinsLocale::GetCaseFirst(EcmaRuntimeCallInfo *argv)
202{
203    // This property only exists if %Locale%.[[RelevantExtensionKeys]] contains "kf".
204    JSThread *thread = argv->GetThread();
205    BUILTINS_API_TRACE(thread, Locale, GetCaseFirst);
206    [[maybe_unused]] EcmaHandleScope scope(thread);
207    // 1. Let loc be the this value.
208    JSHandle<JSTaggedValue> loc = GetThis(argv);
209    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
210    if (!loc->IsJSLocale()) {
211        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
212    }
213    // 3. Return loc.[[CaseFirst]].
214    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
215    JSTaggedValue caseFirst = JSLocale::NormalizeKeywordValue(thread, locale, "kf");
216    return caseFirst;
217}
218
219JSTaggedValue BuiltinsLocale::GetCollation(EcmaRuntimeCallInfo *argv)
220{
221    JSThread *thread = argv->GetThread();
222    BUILTINS_API_TRACE(thread, Locale, GetCollation);
223    [[maybe_unused]] EcmaHandleScope scope(thread);
224    // 1. Let loc be the this value.
225    JSHandle<JSTaggedValue> loc = GetThis(argv);
226    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
227    if (!loc->IsJSLocale()) {
228        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
229    }
230    // 3. Return loc.[[Collation]].
231    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
232    JSTaggedValue collation = JSLocale::NormalizeKeywordValue(thread, locale, "co");
233    return collation;
234}
235
236JSTaggedValue BuiltinsLocale::GetHourCycle(EcmaRuntimeCallInfo *argv)
237{
238    JSThread *thread = argv->GetThread();
239    BUILTINS_API_TRACE(thread, Locale, GetHourCycle);
240    [[maybe_unused]] EcmaHandleScope scope(thread);
241    // 1. Let loc be the this value.
242    JSHandle<JSTaggedValue> loc = GetThis(argv);
243    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
244    if (!loc->IsJSLocale()) {
245        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
246    }
247    // 3. Return loc.[[HourCycle]].
248    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
249    JSTaggedValue hourCycle = JSLocale::NormalizeKeywordValue(thread, locale, "hc");
250    return hourCycle;
251}
252
253JSTaggedValue BuiltinsLocale::GetNumeric(EcmaRuntimeCallInfo *argv)
254{
255    // This property only exists if %Locale%.[[RelevantExtensionKeys]] contains "kn".
256    JSThread *thread = argv->GetThread();
257    BUILTINS_API_TRACE(thread, Locale, GetNumeric);
258    [[maybe_unused]] EcmaHandleScope scope(thread);
259    // 1. Let loc be the this value.
260    JSHandle<JSTaggedValue> loc = GetThis(argv);
261    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
262    if (!loc->IsJSLocale()) {
263        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
264    }
265    // 3. Return loc.[[Numeric]].
266    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
267    icu::Locale *icuLocale = locale->GetIcuLocale();
268    UErrorCode status = U_ZERO_ERROR;
269    auto numeric = icuLocale->getUnicodeKeywordValue<CString>("kn", status);
270    JSTaggedValue result = (numeric == "true") ? JSTaggedValue::True() : JSTaggedValue::False();
271    return result;
272}
273
274JSTaggedValue BuiltinsLocale::GetNumberingSystem(EcmaRuntimeCallInfo *argv)
275{
276    JSThread *thread = argv->GetThread();
277    BUILTINS_API_TRACE(thread, Locale, GetNumberingSystem);
278    [[maybe_unused]] EcmaHandleScope scope(thread);
279    // 1. Let loc be the this value.
280    JSHandle<JSTaggedValue> loc = GetThis(argv);
281    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
282    if (!loc->IsJSLocale()) {
283        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
284    }
285    // 3. Return loc.[[NumberingSystem]].
286    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
287    JSTaggedValue numberingSystem = JSLocale::NormalizeKeywordValue(thread, locale, "nu");
288    return numberingSystem;
289}
290
291JSTaggedValue BuiltinsLocale::GetLanguage(EcmaRuntimeCallInfo *argv)
292{
293    JSThread *thread = argv->GetThread();
294    BUILTINS_API_TRACE(thread, Locale, GetLanguage);
295    [[maybe_unused]] EcmaHandleScope scope(thread);
296    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
297    // 1. Let loc be the this value.
298    JSHandle<JSTaggedValue> loc = GetThis(argv);
299    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
300    if (!loc->IsJSLocale()) {
301        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
302    }
303    // 3. Let locale be loc.[[Locale]].
304    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
305    // 4. Assert: locale matches the unicode_locale_id production.
306    // 5. Return the substring of locale corresponding to the unicode_language_subtag production of the
307    //    unicode_language_id.
308    JSHandle<EcmaString> result = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledUndefinedString());
309    CString language = locale->GetIcuLocale()->getLanguage();
310    if (language.empty()) {
311        return result.GetTaggedValue();
312    }
313    result = factory->NewFromUtf8(language);
314    return result.GetTaggedValue();
315}
316
317JSTaggedValue BuiltinsLocale::GetScript(EcmaRuntimeCallInfo *argv)
318{
319    JSThread *thread = argv->GetThread();
320    BUILTINS_API_TRACE(thread, Locale, GetScript);
321    [[maybe_unused]] EcmaHandleScope scope(thread);
322    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
323    // 1. Let loc be the this value.
324    JSHandle<JSTaggedValue> loc = GetThis(argv);
325    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
326    if (!loc->IsJSLocale()) {
327        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
328    }
329    // 3. Let locale be loc.[[Locale]].
330    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
331
332    // 4. Assert: locale matches the unicode_locale_id production.
333    // 5. If the unicode_language_id production of locale does not contain the ["-" unicode_script_subtag] sequence,
334    //    return undefined.
335    // 6. Return the substring of locale corresponding to the unicode_script_subtag production of the
336    //    unicode_language_id.
337    JSHandle<EcmaString> result(thread, JSTaggedValue::Undefined());
338    CString script = locale->GetIcuLocale()->getScript();
339    if (script.empty()) {
340        return result.GetTaggedValue();
341    }
342    result = factory->NewFromUtf8(script);
343    return result.GetTaggedValue();
344}
345
346JSTaggedValue BuiltinsLocale::GetRegion(EcmaRuntimeCallInfo *argv)
347{
348    JSThread *thread = argv->GetThread();
349    BUILTINS_API_TRACE(thread, Locale, GetRegion);
350    [[maybe_unused]] EcmaHandleScope scope(thread);
351    EcmaVM *ecmaVm = thread->GetEcmaVM();
352    const GlobalEnvConstants *globalConst = thread->GlobalConstants();
353    ObjectFactory *factory = ecmaVm->GetFactory();
354    // 1. Let loc be the this value.
355    JSHandle<JSTaggedValue> loc = GetThis(argv);
356    // 2. Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
357    if (!loc->IsJSLocale()) {
358        THROW_TYPE_ERROR_AND_RETURN(thread, "not locale", JSTaggedValue::Exception());
359    }
360    // 3. Let locale be loc.[[Locale]].
361    JSHandle<JSLocale> locale = JSHandle<JSLocale>::Cast(loc);
362    // 4. Assert: locale matches the unicode_locale_id production.
363    // 5. If the unicode_language_id production of locale does not contain the ["-" unicode_region_subtag] sequence,
364    //    return undefined.
365    // 6. Return the substring of locale corresponding to the unicode_region_subtag production of the
366    //    unicode_language_id.
367    CString region = locale->GetIcuLocale()->getCountry();
368    if (region.empty()) {
369        return globalConst->GetUndefined();
370    }
371    return factory->NewFromUtf8(region).GetTaggedValue();
372}
373}  // namespace panda::ecmascript::builtins
374