1/*
2 * Copyright (c) 2023 Shenzhen Kaihong Digital Industry Development 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_segmenter.h"
17
18#include <cstring>
19
20#include "ecmascript/intl/locale_helper.h"
21#include "ecmascript/object_factory-inl.h"
22
23namespace panda::ecmascript {
24
25void JSSegmenter::SetIcuBreakIterator(JSThread *thread, const JSHandle<JSSegmenter> &segmenter,
26                                      icu::BreakIterator* icuBreakIterator, const NativePointerCallback &callback)
27{
28    EcmaVM *ecmaVm = thread->GetEcmaVM();
29    ObjectFactory *factory = ecmaVm->GetFactory();
30
31    ASSERT(icuBreakIterator != nullptr);
32    JSTaggedValue data = segmenter->GetIcuField();
33    if (data.IsJSNativePointer()) {
34        JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
35        native->ResetExternalPointer(thread, icuBreakIterator);
36        return;
37    }
38    JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuBreakIterator, callback);
39    segmenter->SetIcuField(thread, pointer.GetTaggedValue());
40}
41
42JSHandle<TaggedArray> JSSegmenter::GetAvailableLocales(JSThread *thread)
43{
44    std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, nullptr, nullptr);
45    JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
46    return availableLocales;
47}
48
49void JSSegmenter::InitializeIcuBreakIterator(JSThread *thread, const JSHandle<JSSegmenter> &segmenter,
50                                             const icu::Locale &icuLocale, GranularityOption granularity)
51{
52    UErrorCode status = U_ZERO_ERROR;
53    std::unique_ptr<icu::BreakIterator> icuBreakIterator;
54
55    switch (granularity) {
56        case GranularityOption::GRAPHEME:
57            icuBreakIterator.reset(icu::BreakIterator::createCharacterInstance(icuLocale, status));
58            break;
59        case GranularityOption::WORD:
60            icuBreakIterator.reset(icu::BreakIterator::createWordInstance(icuLocale, status));
61            break;
62        case GranularityOption::SENTENCE:
63            icuBreakIterator.reset(icu::BreakIterator::createSentenceInstance(icuLocale, status));
64            break;
65        default:
66            LOG_ECMA(FATAL) << "this branch is unreachable";
67            UNREACHABLE();
68    }
69    if (U_FAILURE(status) || icuBreakIterator == nullptr) {
70        if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) {
71            THROW_ERROR(thread, ErrorType::REFERENCE_ERROR, "can not find icu data resources");
72        }
73        THROW_ERROR(thread, ErrorType::RANGE_ERROR, "create icu::BreakIterator failed");
74    }
75
76    SetIcuBreakIterator(thread, segmenter, icuBreakIterator.release(), JSSegmenter::FreeIcuBreakIterator);
77}
78
79JSHandle<JSSegmenter> JSSegmenter::InitializeSegmenter(JSThread *thread,
80                                                       const JSHandle<JSSegmenter> &segmenter,
81                                                       const JSHandle<JSTaggedValue> &locales,
82                                                       const JSHandle<JSTaggedValue> &options)
83{
84    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
85    auto globalConst = thread->GlobalConstants();
86    // 4. Let requestedLocales be ? CanonicalizeLocaleList(locales).
87    JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
88    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSSegmenter, thread);
89
90    // 5. Let options be ? GetOptionsObject(options).
91    JSHandle<JSObject> optionsObject;
92    if (options->IsUndefined()) {
93        optionsObject = factory->CreateNullJSObject();
94    } else if (!options->IsJSObject()) {
95        THROW_TYPE_ERROR_AND_RETURN(thread, "options is not Object", segmenter);
96    } else {
97        optionsObject = JSTaggedValue::ToObject(thread, options);
98        RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSSegmenter, thread);
99    }
100
101    // 6. Let opt be a new Record.
102    // 7. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
103    JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
104    auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
105        thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
106        {"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
107    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSSegmenter, thread);
108
109    // 9. Let localeData be %Segmenter%.[[LocaleData]].
110    // 10. Let r be ResolveLocale(%Segmenter%.[[AvailableLocales]], requestedLocales, opt,
111    // %Segmenter%.[[RelevantExtensionKeys]], localeData).
112    JSHandle<TaggedArray> availableLocales;
113    if (requestedLocales->GetLength() == 0) {
114        availableLocales = factory->EmptyArray();
115    } else {
116        availableLocales = JSSegmenter::GetAvailableLocales(thread);
117    }
118    std::set<std::string> relevantExtensionKeys {""};
119    ResolvedLocale r =
120        JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
121    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSSegmenter, thread);
122
123    // 11. Set segmenter.[[Locale]] to r.[[locale]].
124    icu::Locale icuLocale = r.localeData;
125    JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
126    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSSegmenter, thread);
127    segmenter->SetLocale(thread, localeStr.GetTaggedValue());
128
129    // 12. Let granularity be ? GetOption(options, "granularity", string, « "grapheme", "word", "sentence" »,
130    //     "grapheme").
131    property = globalConst->GetHandledGranularityString();
132    auto granularity = JSLocale::GetOptionOfString<GranularityOption>(thread, optionsObject, property,
133                                                                      {GranularityOption::GRAPHEME,
134                                                                      GranularityOption::WORD,
135                                                                      GranularityOption::SENTENCE},
136                                                                      {"grapheme", "word", "sentence"},
137                                                                      GranularityOption::GRAPHEME);
138    RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSSegmenter, thread);
139
140    // 13. Set segmenter.[[SegmenterGranularity]] to granularity.
141    segmenter->SetGranularity(granularity);
142    InitializeIcuBreakIterator(thread, segmenter, icuLocale, granularity);
143    // 14. Return segmenter.
144    return segmenter;
145}
146
147JSHandle<JSTaggedValue> JSSegmenter::GranularityOptionToEcmaString(JSThread *thread, GranularityOption granularity)
148{
149    JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
150    auto globalConst = thread->GlobalConstants();
151    switch (granularity) {
152        case GranularityOption::GRAPHEME:
153            result.Update(globalConst->GetHandledGraphemeString().GetTaggedValue());
154            break;
155        case GranularityOption::WORD:
156            result.Update(globalConst->GetHandledWordString().GetTaggedValue());
157            break;
158        case GranularityOption::SENTENCE:
159            result.Update(globalConst->GetHandledSentenceString().GetTaggedValue());
160            break;
161        default:
162            LOG_ECMA(FATAL) << "this branch is unreachable";
163            UNREACHABLE();
164    }
165    return result;
166}
167
168void JSSegmenter::ResolvedOptions(JSThread *thread, const JSHandle<JSSegmenter> &segmenter,
169                                  const JSHandle<JSObject> &options)
170{
171    auto globalConst = thread->GlobalConstants();
172
173    // [[Locale]]
174    JSHandle<JSTaggedValue> propertyKey = globalConst->GetHandledLocaleString();
175    JSHandle<JSTaggedValue> locale(thread, segmenter->GetLocale());
176    JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, locale);
177    RETURN_IF_ABRUPT_COMPLETION(thread);
178
179    // [[SegmenterGranularity]]
180    GranularityOption granularity = segmenter->GetGranularity();
181    propertyKey = globalConst->GetHandledGranularityString();
182    JSHandle<JSTaggedValue> granularityString = GranularityOptionToEcmaString(thread, granularity);
183    JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, granularityString);
184    RETURN_IF_ABRUPT_COMPLETION(thread);
185}
186}  // namespace panda::ecmascript
187