1/*
2 * Copyright (c) 2022-2024 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/containers/containers_treeset.h"
17
18#include "ecmascript/containers/containers_errors.h"
19#include "ecmascript/interpreter/interpreter.h"
20#include "ecmascript/js_api/js_api_tree_set.h"
21#include "ecmascript/js_api/js_api_tree_set_iterator.h"
22#include "ecmascript/js_function.h"
23#include "ecmascript/tagged_tree.h"
24
25namespace panda::ecmascript::containers {
26JSTaggedValue ContainersTreeSet::TreeSetConstructor(EcmaRuntimeCallInfo *argv)
27{
28    ASSERT(argv);
29    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, Constructor);
30    JSThread *thread = argv->GetThread();
31    [[maybe_unused]] EcmaHandleScope handleScope(thread);
32    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
33
34    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
35    if (newTarget->IsUndefined()) {
36        JSTaggedValue error =
37            ContainerError::BusinessError(thread, ErrorFlag::IS_NULL_ERROR,
38                                          "The TreeSet's constructor cannot be directly invoked");
39        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
40    }
41    // new TreeSet
42    JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
43    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
44    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
45
46    // Set set’s internal slot with a new empty List.
47    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(obj);
48    JSTaggedValue internal = TaggedTreeSet::Create(thread);
49    set->SetTreeSet(thread, internal);
50
51    // If comparefn was supplied, let compare be comparefn; else let compare be hole.
52    JSHandle<JSTaggedValue> compareFn(GetCallArg(argv, 0));
53    if (compareFn->IsUndefined() || compareFn->IsNull()) {
54        return set.GetTaggedValue();
55    }
56    if (!compareFn->IsCallable()) {
57        JSHandle<EcmaString> result = JSTaggedValue::ToString(thread, compareFn.GetTaggedValue());
58        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
59        CString errorMsg =
60            "The type of \"comparefn\" must be callable. Received value is: " + ConvertToString(*result);
61        JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str());
62        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
63    }
64
65    TaggedTreeSet::Cast(internal.GetTaggedObject())->SetCompare(thread, compareFn.GetTaggedValue());
66    return set.GetTaggedValue();
67}
68
69JSTaggedValue ContainersTreeSet::Add(EcmaRuntimeCallInfo *argv)
70{
71    ASSERT(argv);
72    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, Add);
73    JSThread *thread = argv->GetThread();
74    [[maybe_unused]] EcmaHandleScope handleScope(thread);
75    // get and check this set
76    JSHandle<JSTaggedValue> self = GetThis(argv);
77    if (!self->IsJSAPITreeSet()) {
78        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
79            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
80        } else {
81            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
82                                                                "The add method cannot be bound");
83            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
84        }
85    }
86
87    JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
88    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
89    JSAPITreeSet::Add(thread, set, value);
90    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
91    return JSTaggedValue::True();
92}
93
94JSTaggedValue ContainersTreeSet::Remove(EcmaRuntimeCallInfo *argv)
95{
96    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, Remove);
97    JSThread *thread = argv->GetThread();
98    [[maybe_unused]] EcmaHandleScope handleScope(thread);
99    // get and check this set
100    JSHandle<JSTaggedValue> self = GetThis(argv);
101    if (!self->IsJSAPITreeSet()) {
102        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
103            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
104        } else {
105            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
106                                                                "The remove method cannot be bound");
107            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
108        }
109    }
110
111    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
112    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
113    return GetTaggedBoolean(JSAPITreeSet::Delete(thread, set, key));
114}
115
116JSTaggedValue ContainersTreeSet::Has(EcmaRuntimeCallInfo *argv)
117{
118    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, Has);
119    JSThread *thread = argv->GetThread();
120    [[maybe_unused]] EcmaHandleScope handleScope(thread);
121    // get and check this set
122    JSHandle<JSTaggedValue> self(GetThis(argv));
123    if (!self->IsJSAPITreeSet()) {
124        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
125            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
126        } else {
127            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
128                                                                "The has method cannot be bound");
129            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
130        }
131    }
132
133    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
134    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
135
136    bool flag = JSAPITreeSet::Has(thread, JSHandle<JSAPITreeSet>::Cast(set), key);
137    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
138    return GetTaggedBoolean(flag);
139}
140
141JSTaggedValue ContainersTreeSet::GetFirstValue(EcmaRuntimeCallInfo *argv)
142{
143    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, GetFirstValue);
144    JSThread *thread = argv->GetThread();
145    [[maybe_unused]] EcmaHandleScope handleScope(thread);
146    // get and check this set
147    JSHandle<JSTaggedValue> self(GetThis(argv));
148    if (!self->IsJSAPITreeSet()) {
149        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
150            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
151        } else {
152            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
153                                                                "The getFirstValue method cannot be bound");
154            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
155        }
156    }
157
158    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
159    return TaggedTreeSet::Cast(set->GetTreeSet().GetTaggedObject())->GetFirstKey();
160}
161
162JSTaggedValue ContainersTreeSet::GetLastValue(EcmaRuntimeCallInfo *argv)
163{
164    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, GetLastValue);
165    JSThread *thread = argv->GetThread();
166    [[maybe_unused]] EcmaHandleScope handleScope(thread);
167    // get and check this set
168    JSHandle<JSTaggedValue> self(GetThis(argv));
169    if (!self->IsJSAPITreeSet()) {
170        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
171            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
172        } else {
173            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
174                                                                "The getLastValue method cannot be bound");
175            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
176        }
177    }
178
179    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
180    return TaggedTreeSet::Cast(set->GetTreeSet().GetTaggedObject())->GetLastKey();
181}
182
183JSTaggedValue ContainersTreeSet::Clear(EcmaRuntimeCallInfo *argv)
184{
185    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, Clear);
186    JSThread *thread = argv->GetThread();
187    [[maybe_unused]] EcmaHandleScope handleScope(thread);
188    // get and check this set
189    JSHandle<JSTaggedValue> self(GetThis(argv));
190    if (!self->IsJSAPITreeSet()) {
191        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
192            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
193        } else {
194            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
195                                                                "The clear method cannot be bound");
196            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
197        }
198    }
199
200    JSAPITreeSet::Clear(thread, JSHandle<JSAPITreeSet>::Cast(self));
201    return JSTaggedValue::Undefined();
202}
203
204JSTaggedValue ContainersTreeSet::GetLowerValue(EcmaRuntimeCallInfo *argv)
205{
206    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, GetLowerValue);
207    JSThread *thread = argv->GetThread();
208    [[maybe_unused]] EcmaHandleScope handleScope(thread);
209    // get and check this set
210    JSHandle<JSTaggedValue> self(GetThis(argv));
211    if (!self->IsJSAPITreeSet()) {
212        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
213            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
214        } else {
215            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
216                                                                "The getLowerValue method cannot be bound");
217            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
218        }
219    }
220
221    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
222    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
223    JSHandle<TaggedTreeSet> tset(thread, set->GetTreeSet());
224    return TaggedTreeSet::GetLowerKey(thread, tset, key);
225}
226
227JSTaggedValue ContainersTreeSet::GetHigherValue(EcmaRuntimeCallInfo *argv)
228{
229    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, GetHigherValue);
230    JSThread *thread = argv->GetThread();
231    [[maybe_unused]] EcmaHandleScope handleScope(thread);
232    // get and check this set
233    JSHandle<JSTaggedValue> self(GetThis(argv));
234    if (!self->IsJSAPITreeSet()) {
235        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
236            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
237        } else {
238            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
239                                                                "The getHigherValue method cannot be bound");
240            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
241        }
242    }
243
244    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
245    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
246    JSHandle<TaggedTreeSet> tset(thread, set->GetTreeSet());
247    return TaggedTreeSet::GetHigherKey(thread, tset, key);
248}
249
250JSTaggedValue ContainersTreeSet::PopFirst(EcmaRuntimeCallInfo *argv)
251{
252    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, PopFirst);
253    JSThread *thread = argv->GetThread();
254    [[maybe_unused]] EcmaHandleScope handleScope(thread);
255    // get and check this set
256    JSHandle<JSTaggedValue> self(GetThis(argv));
257    if (!self->IsJSAPITreeSet()) {
258        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
259            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
260        } else {
261            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
262                                                                "The popFirst method cannot be bound");
263            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
264        }
265    }
266
267    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
268    return JSAPITreeSet::PopFirst(thread, set);
269}
270
271JSTaggedValue ContainersTreeSet::PopLast(EcmaRuntimeCallInfo *argv)
272{
273    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, PopLast);
274    JSThread *thread = argv->GetThread();
275    [[maybe_unused]] EcmaHandleScope handleScope(thread);
276    // get and check this set
277    JSHandle<JSTaggedValue> self(GetThis(argv));
278    if (!self->IsJSAPITreeSet()) {
279        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
280            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
281        } else {
282            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
283                                                                "The popLast method cannot be bound");
284            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
285        }
286    }
287
288    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
289    return JSAPITreeSet::PopLast(thread, set);
290}
291
292JSTaggedValue ContainersTreeSet::IsEmpty(EcmaRuntimeCallInfo *argv)
293{
294    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, IsEmpty);
295    JSThread *thread = argv->GetThread();
296    [[maybe_unused]] EcmaHandleScope handleScope(thread);
297    // get and check this set
298    JSHandle<JSTaggedValue> self = GetThis(argv);
299    if (!self->IsJSAPITreeSet()) {
300        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
301            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
302        } else {
303            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
304                                                                "The isEmpty method cannot be bound");
305            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
306        }
307    }
308    JSHandle<JSAPITreeSet> set = JSHandle<JSAPITreeSet>::Cast(self);
309    return GetTaggedBoolean(set->GetSize() == 0);
310}
311
312JSTaggedValue ContainersTreeSet::Values(EcmaRuntimeCallInfo *argv)
313{
314    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, Values);
315    JSThread *thread = argv->GetThread();
316    [[maybe_unused]] EcmaHandleScope handleScope(thread);
317    JSHandle<JSTaggedValue> self = GetThis(argv);
318    JSHandle<JSTaggedValue> iter = JSAPITreeSetIterator::CreateTreeSetIterator(thread, self, IterationKind::KEY);
319    return iter.GetTaggedValue();
320}
321
322JSTaggedValue ContainersTreeSet::Entries(EcmaRuntimeCallInfo *argv)
323{
324    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, Entries);
325    JSThread *thread = argv->GetThread();
326    [[maybe_unused]] EcmaHandleScope handleScope(thread);
327    JSHandle<JSTaggedValue> self = GetThis(argv);
328    JSHandle<JSTaggedValue> iter =
329        JSAPITreeSetIterator::CreateTreeSetIterator(thread, self, IterationKind::KEY_AND_VALUE);
330    return iter.GetTaggedValue();
331}
332
333JSTaggedValue ContainersTreeSet::ForEach(EcmaRuntimeCallInfo *argv)
334{
335    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, ForEach);
336    JSThread *thread = argv->GetThread();
337    [[maybe_unused]] EcmaHandleScope handleScope(thread);
338    // get and check TreeSet object
339    JSHandle<JSTaggedValue> self = GetThis(argv);
340    if (!self->IsJSAPITreeSet()) {
341        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
342            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
343        } else {
344            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
345                                                                "The forEach method cannot be bound");
346            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
347        }
348    }
349    // get and check callback function
350    JSHandle<JSTaggedValue> func(GetCallArg(argv, 0));
351    if (!func->IsCallable()) {
352        JSHandle<EcmaString> result = JSTaggedValue::ToString(thread, func.GetTaggedValue());
353        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
354        CString errorMsg =
355            "The type of \"callbackfn\" must be callable. Received value is: " + ConvertToString(*result);
356        JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str());
357        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
358    }
359    // If thisArg was supplied, let T be thisArg; else let T be undefined.
360    JSHandle<JSTaggedValue> thisArg = GetCallArg(argv, 1);
361    JSHandle<JSAPITreeSet> tset = JSHandle<JSAPITreeSet>::Cast(self);
362    JSMutableHandle<TaggedTreeSet> iteratedSet(thread, tset->GetTreeSet());
363    uint32_t elements = iteratedSet->NumberOfElements();
364    JSHandle<TaggedArray> entries = TaggedTreeSet::GetArrayFromSet(thread, iteratedSet);
365    uint32_t index = 0;
366    size_t length = entries->GetLength();
367    const uint32_t argsLength = 3;
368    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
369    JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
370    while (index < elements) {
371        int entriesIndex = entries->Get(index).GetInt();
372        key.Update(iteratedSet->GetKey(entriesIndex));
373        // Let funcResult be Call(callbackfn, T, «e, e, S»).
374        EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, func, thisArg, undefined, argsLength);
375        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
376        info->SetCallArg(key.GetTaggedValue(), key.GetTaggedValue(), self.GetTaggedValue());
377        JSTaggedValue ret = JSFunction::Call(info);
378        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, ret);
379        // check entries should be update, size will be update by set add and remove.
380        if (tset->GetSize() != static_cast<int>(length)) {
381            iteratedSet.Update(tset->GetTreeSet());
382            entries = TaggedTreeSet::GetArrayFromSet(thread, iteratedSet);
383            elements = iteratedSet->NumberOfElements();
384            length = entries->GetLength();
385        }
386        index++;
387    }
388    return JSTaggedValue::Undefined();
389}
390
391JSTaggedValue ContainersTreeSet::GetLength(EcmaRuntimeCallInfo *argv)
392{
393    ASSERT(argv);
394    BUILTINS_API_TRACE(argv->GetThread(), TreeSet, GetLength);
395    JSThread *thread = argv->GetThread();
396    [[maybe_unused]] EcmaHandleScope handleScope(thread);
397    // get and check this set
398    JSHandle<JSTaggedValue> self(GetThis(argv));
399    if (!self->IsJSAPITreeSet()) {
400        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPITreeSet()) {
401            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
402        } else {
403            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
404                                                                "The getLength method cannot be bound");
405            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
406        }
407    }
408
409    int count = JSHandle<JSAPITreeSet>::Cast(self)->GetSize();
410    return JSTaggedValue(count);
411}
412}  // namespace panda::ecmascript::containers
413