1/*
2 * Copyright (c) 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/builtins/builtins_shared_set.h"
17
18#include "ecmascript/js_function.h"
19#include "ecmascript/interpreter/interpreter.h"
20
21#include "ecmascript/linked_hash_table.h"
22#include "ecmascript/shared_objects/concurrent_api_scope.h"
23#include "ecmascript/shared_objects/js_shared_set_iterator.h"
24
25namespace panda::ecmascript::builtins {
26JSTaggedValue BuiltinsSharedSet::Constructor(EcmaRuntimeCallInfo *argv)
27{
28    ASSERT(argv);
29    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, Constructor);
30    JSThread *thread = argv->GetThread();
31    [[maybe_unused]] EcmaHandleScope handleScope(thread);
32    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
33    // 1. If NewTarget is undefined, throw exception
34    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
35    if (newTarget->IsUndefined()) {
36        JSTaggedValue error = containers::ContainerError::BusinessError(
37            thread, containers::ErrorFlag::IS_NULL_ERROR, "The ArkTS Set's constructor cannot be directly invoked.");
38        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
39    }
40    // 2.Let set be OrdinaryCreateFromConstructor(NewTarget, "%SetPrototype%", «‍[[SetData]]» ).
41    JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
42    ASSERT(constructor->IsJSSharedFunction() && constructor.GetTaggedValue().IsInSharedHeap());
43    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
44    // 3.returnIfAbrupt()
45    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
46    ASSERT(obj.GetTaggedValue().IsInSharedHeap());
47    JSHandle<JSSharedSet> set = JSHandle<JSSharedSet>::Cast(obj);
48    // 3.ReturnIfAbrupt(set).
49    // 4.Set set’s [[SetData]] internal slot to a new empty List.
50    JSHandle<LinkedHashSet> linkedSet = LinkedHashSet::Create(thread,
51        LinkedHashSet::MIN_CAPACITY, MemSpaceKind::SHARED);
52    set->SetLinkedSet(thread, linkedSet);
53    // add data into set from iterable
54    // 5.If iterable is not present, let iterable be undefined.
55    // 6.If iterable is either undefined or null, let iter be undefined.
56    JSHandle<JSTaggedValue> iterable(GetCallArg(argv, 0));
57    // 8.If iter is undefined, return set
58    if (iterable->IsUndefined() || iterable->IsNull()) {
59        return set.GetTaggedValue();
60    }
61    // Let adder be Get(set, "add").
62    JSHandle<JSTaggedValue> adderKey(thread->GlobalConstants()->GetHandledAddString());
63    JSHandle<JSTaggedValue> setHandle(set);
64    JSHandle<JSTaggedValue> adder = JSObject::GetProperty(thread, setHandle, adderKey).GetValue();
65    // ReturnIfAbrupt(adder).
66    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, adder.GetTaggedValue());
67    // If IsCallable(adder) is false, throw a TypeError exception
68    if (!adder->IsCallable()) {
69        THROW_TYPE_ERROR_AND_RETURN(thread, "adder is not callable", adder.GetTaggedValue());
70    }
71    // Let iter be GetIterator(iterable).
72    JSHandle<JSTaggedValue> iter(JSIterator::GetIterator(thread, iterable));
73    // ReturnIfAbrupt(iter).
74    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, iter.GetTaggedValue());
75    // values in iterator_result may be a JSArray, values[0] = key values[1]=value, used valueIndex to get value from
76    // jsarray
77    JSHandle<JSTaggedValue> valueIndex(thread, JSTaggedValue(1));
78    JSHandle<JSTaggedValue> next = JSIterator::IteratorStep(thread, iter);
79    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
80    while (!next->IsFalse()) {
81        // Let nextValue be IteratorValue(next).
82        JSHandle<JSTaggedValue> nextValue(JSIterator::IteratorValue(thread, next));
83        // ReturnIfAbrupt(nextValue).
84        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, nextValue.GetTaggedValue());
85        JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
86        EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, adder, setHandle, undefined, 1);
87        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, nextValue.GetTaggedValue());
88        info->SetCallArg(nextValue.GetTaggedValue());
89        if (nextValue->IsArray(thread)) {
90            auto prop = JSTaggedValue::GetProperty(thread, nextValue, valueIndex).GetValue();
91            info->SetCallArg(prop.GetTaggedValue());
92        }
93        JSFunction::Call(info);
94        // Let status be Call(adder, set, «nextValue.[[value]]»).
95        if (thread->HasPendingException()) {
96            return JSIterator::IteratorCloseAndReturn(thread, iter);
97        }
98        // Let next be IteratorStep(iter).
99        next = JSIterator::IteratorStep(thread, iter);
100        // ReturnIfAbrupt(next).
101        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
102    }
103    return set.GetTaggedValue();
104}
105
106JSTaggedValue BuiltinsSharedSet::Add(EcmaRuntimeCallInfo *argv)
107{
108    ASSERT(argv);
109    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, Add);
110    JSThread *thread = argv->GetThread();
111    [[maybe_unused]] EcmaHandleScope handleScope(thread);
112    JSHandle<JSTaggedValue> self = GetThis(argv);
113    if (!self->IsJSSharedSet()) {
114        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
115                                                               "The add method cannot be bound.");
116        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
117    }
118    JSHandle<JSTaggedValue> value(GetCallArg(argv, 0));
119    JSHandle<JSSharedSet> set(self);
120    JSSharedSet::Add(thread, set, value);
121    return set.GetTaggedValue();
122}
123
124JSTaggedValue BuiltinsSharedSet::Clear(EcmaRuntimeCallInfo *argv)
125{
126    ASSERT(argv);
127    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, Clear);
128    JSThread *thread = argv->GetThread();
129    [[maybe_unused]] EcmaHandleScope handleScope(thread);
130    JSHandle<JSTaggedValue> self = GetThis(argv);
131    if (!self->IsJSSharedSet()) {
132        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
133                                                               "The clear method cannot be bound.");
134        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
135    }
136    JSHandle<JSSharedSet> set(self);
137    JSSharedSet::Clear(thread, set);
138    return JSTaggedValue::Undefined();
139}
140
141JSTaggedValue BuiltinsSharedSet::Delete(EcmaRuntimeCallInfo *argv)
142{
143    ASSERT(argv);
144    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, Delete);
145    JSThread *thread = argv->GetThread();
146    [[maybe_unused]] EcmaHandleScope handleScope(thread);
147    JSHandle<JSTaggedValue> self = GetThis(argv);
148    if (!self->IsJSSharedSet()) {
149        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
150                                                               "The delete method cannot be bound.");
151        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
152    }
153    JSHandle<JSSharedSet> set(self);
154    JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
155    bool flag = JSSharedSet::Delete(thread, set, value);
156    return GetTaggedBoolean(flag);
157}
158
159JSTaggedValue BuiltinsSharedSet::Has(EcmaRuntimeCallInfo *argv)
160{
161    ASSERT(argv);
162    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, Has);
163    JSThread *thread = argv->GetThread();
164    [[maybe_unused]] EcmaHandleScope handleScope(thread);
165    JSHandle<JSTaggedValue> self = GetThis(argv);
166    if (!self->IsJSSharedSet()) {
167        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
168                                                               "The has method cannot be bound.");
169        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
170    }
171    JSHandle<JSSharedSet> set(self);
172    JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
173    bool flag = JSSharedSet::Has(thread, set, value.GetTaggedValue());
174    return GetTaggedBoolean(flag);
175}
176
177JSTaggedValue BuiltinsSharedSet::ForEach(EcmaRuntimeCallInfo *argv)
178{
179    JSThread *thread = argv->GetThread();
180    BUILTINS_API_TRACE(thread, SharedSet, ForEach);
181    [[maybe_unused]] EcmaHandleScope handleScope(thread);
182    JSHandle<JSTaggedValue> self = GetThis(argv);
183    if (!self->IsJSSharedSet()) {
184        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
185                                                               "The forEach method cannot be bound.");
186        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
187    }
188    [[maybe_unused]] ConcurrentApiScope<JSSharedSet> scope(thread, self);
189    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
190
191    JSHandle<JSSharedSet> set(self);
192    JSHandle<JSTaggedValue> func(GetCallArg(argv, 0));
193    if (!func->IsCallable()) {
194        THROW_TYPE_ERROR_AND_RETURN(thread, "callback is not callable", JSTaggedValue::Exception());
195    }
196    JSHandle<JSTaggedValue> thisArg = GetCallArg(argv, 1);
197    JSMutableHandle<LinkedHashSet> hashSet(thread, set->GetLinkedSet());
198    const uint32_t argsLength = 3;
199    int index = 0;
200    int totalElements = hashSet->NumberOfElements() + hashSet->NumberOfDeletedElements();
201    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
202    // Repeat for each e that is an element of entries, in original insertion order
203    while (index < totalElements) {
204        JSHandle<JSTaggedValue> key(thread, hashSet->GetKey(index++));
205        if (!key->IsHole()) {
206            EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(
207                thread, func, thisArg, undefined, argsLength);
208            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
209            info->SetCallArg(key.GetTaggedValue(), key.GetTaggedValue(), set.GetTaggedValue());
210            JSTaggedValue ret = JSFunction::Call(info);
211            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, ret);
212        }
213    }
214    return JSTaggedValue::Undefined();
215}
216
217JSTaggedValue BuiltinsSharedSet::Species(EcmaRuntimeCallInfo *argv)
218{
219    return GetThis(argv).GetTaggedValue();
220}
221
222JSTaggedValue BuiltinsSharedSet::GetSize(EcmaRuntimeCallInfo *argv)
223{
224    ASSERT(argv);
225    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, GetSize);
226    JSThread *thread = argv->GetThread();
227    [[maybe_unused]] EcmaHandleScope handleScope(thread);
228    JSHandle<JSTaggedValue> self(GetThis(argv));
229    if (!self->IsJSSharedSet()) {
230        THROW_TYPE_ERROR_AND_RETURN(thread, "obj is not SharedSet", JSTaggedValue::Exception());
231    }
232    JSHandle<JSSharedSet> set(self);
233    uint32_t size = JSSharedSet::GetSize(thread, set);
234    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue(0));
235    return JSTaggedValue(size);
236}
237
238JSTaggedValue BuiltinsSharedSet::Entries(EcmaRuntimeCallInfo *argv)
239{
240    ASSERT(argv);
241    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, Entries);
242    JSThread *thread = argv->GetThread();
243    [[maybe_unused]] EcmaHandleScope handleScope(thread);
244    JSHandle<JSTaggedValue> self = GetThis(argv);
245    if (!self->IsJSSharedSet()) {
246        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
247                                                               "The entries method cannot be bound.");
248        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
249    }
250    JSHandle<JSTaggedValue> iter = JSSharedSetIterator::CreateSetIterator(thread, self, IterationKind::KEY_AND_VALUE);
251    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Undefined());
252    return iter.GetTaggedValue();
253}
254
255JSTaggedValue BuiltinsSharedSet::Values(EcmaRuntimeCallInfo *argv)
256{
257    ASSERT(argv);
258    BUILTINS_API_TRACE(argv->GetThread(), SharedSet, Values);
259    JSThread *thread = argv->GetThread();
260    [[maybe_unused]] EcmaHandleScope handleScope(thread);
261    JSHandle<JSTaggedValue> self = GetThis(argv);
262    if (!self->IsJSSharedSet()) {
263        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
264                                                               "The values method cannot be bound.");
265        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
266    }
267    JSHandle<JSTaggedValue> iter = JSSharedSetIterator::CreateSetIterator(thread, self, IterationKind::VALUE);
268    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Undefined());
269    return iter.GetTaggedValue();
270}
271}  // namespace panda::ecmascript::builtins
272