1/*
2 * Copyright (c) 2022 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 "containers_hashset.h"
17
18#include "ecmascript/containers/containers_errors.h"
19#include "ecmascript/interpreter/interpreter.h"
20#include "ecmascript/js_api/js_api_hashset.h"
21#include "ecmascript/js_api/js_api_hashset_iterator.h"
22#include "ecmascript/js_function.h"
23
24namespace panda::ecmascript::containers {
25JSTaggedValue ContainersHashSet::HashSetConstructor(EcmaRuntimeCallInfo *argv)
26{
27    ASSERT(argv != nullptr);
28    JSThread *thread = argv->GetThread();
29    BUILTINS_API_TRACE(thread, HashSet, Constructor);
30    [[maybe_unused]] EcmaHandleScope handleScope(thread);
31    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
32    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
33    if (newTarget->IsUndefined()) {
34        JSTaggedValue error =
35            ContainerError::BusinessError(thread, ErrorFlag::IS_NULL_ERROR,
36                                          "The HashSet's constructor cannot be directly invoked");
37        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
38    }
39
40    JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
41    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
42    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
43
44    JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(obj);
45    JSTaggedValue hashSetArray = TaggedHashArray::Create(thread);
46    hashSet->SetTable(thread, hashSetArray);
47    hashSet->SetSize(0);
48
49    return hashSet.GetTaggedValue();
50}
51
52JSTaggedValue ContainersHashSet::GetIteratorObj(EcmaRuntimeCallInfo *argv)
53{
54    ASSERT(argv != nullptr);
55    JSThread *thread = argv->GetThread();
56    BUILTINS_API_TRACE(thread, HashSet, GetIteratorObj);
57    [[maybe_unused]] EcmaHandleScope handleScope(thread);
58    JSHandle<JSTaggedValue> self = GetThis(argv);
59    if (!self->IsJSAPIHashSet()) {
60        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
61            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
62        } else {
63            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
64                                                                "The Symbol.iterator method cannot be bound");
65            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
66        }
67    }
68    JSHandle<JSTaggedValue> iter = JSAPIHashSetIterator::CreateHashSetIterator(thread, self, IterationKind::VALUE);
69    return iter.GetTaggedValue();
70}
71
72JSTaggedValue ContainersHashSet::Values(EcmaRuntimeCallInfo *argv)
73{
74    ASSERT(argv != nullptr);
75    JSThread *thread = argv->GetThread();
76    BUILTINS_API_TRACE(thread, HashSet, Values);
77    [[maybe_unused]] EcmaHandleScope handleScope(thread);
78    JSHandle<JSTaggedValue> self = GetThis(argv);
79    if (!self->IsJSAPIHashSet()) {
80        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
81            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
82        } else {
83            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
84                                                                "The values method cannot be bound");
85            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
86        }
87    }
88    JSHandle<JSTaggedValue> iter = JSAPIHashSetIterator::CreateHashSetIterator(thread, self, IterationKind::VALUE);
89    return iter.GetTaggedValue();
90}
91
92JSTaggedValue ContainersHashSet::Entries(EcmaRuntimeCallInfo *argv)
93{
94    ASSERT(argv != nullptr);
95    JSThread *thread = argv->GetThread();
96    BUILTINS_API_TRACE(thread, HashSet, Entries);
97    [[maybe_unused]] EcmaHandleScope handleScope(thread);
98    JSHandle<JSTaggedValue> self = GetThis(argv);
99    if (!self->IsJSAPIHashSet()) {
100        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
101            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
102        } else {
103            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
104                                                                "The entries method cannot be bound");
105            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
106        }
107    }
108    JSHandle<JSTaggedValue> iter =
109        JSAPIHashSetIterator::CreateHashSetIterator(thread, self, IterationKind::KEY_AND_VALUE);
110    return iter.GetTaggedValue();
111}
112
113JSTaggedValue ContainersHashSet::Add(EcmaRuntimeCallInfo *argv)
114{
115    ASSERT(argv != nullptr);
116    JSThread *thread = argv->GetThread();
117    BUILTINS_API_TRACE(thread, HashSet, Add);
118    [[maybe_unused]] EcmaHandleScope handleScope(thread);
119    JSHandle<JSTaggedValue> self = GetThis(argv);
120
121    if (!self->IsJSAPIHashSet()) {
122        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
123            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
124        } else {
125            JSTaggedValue error = ContainerError::BusinessError(thread, BIND_ERROR,
126                                                                "The add method cannot be bound");
127            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
128        }
129    }
130
131    JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
132    JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(self);
133    JSAPIHashSet::Add(thread, hashSet, value);
134    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
135    return JSTaggedValue::True();
136}
137
138JSTaggedValue ContainersHashSet::Remove(EcmaRuntimeCallInfo *argv)
139{
140    ASSERT(argv != nullptr);
141    JSThread *thread = argv->GetThread();
142    BUILTINS_API_TRACE(thread, HashSet, Remove);
143    [[maybe_unused]] EcmaHandleScope handleScope(thread);
144    JSHandle<JSTaggedValue> self = GetThis(argv);
145
146    if (!self->IsJSAPIHashSet()) {
147        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
148            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
149        } else {
150            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
151                                                                "The remove method cannot be bound");
152            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
153        }
154    }
155
156    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
157    JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(self);
158    return JSAPIHashSet::Remove(thread, hashSet, key.GetTaggedValue());
159}
160
161JSTaggedValue ContainersHashSet::Has(EcmaRuntimeCallInfo *argv)
162{
163    ASSERT(argv != nullptr);
164    JSThread *thread = argv->GetThread();
165    BUILTINS_API_TRACE(thread, HashSet, Has);
166    [[maybe_unused]] EcmaHandleScope handleScope(thread);
167    JSHandle<JSTaggedValue> self = GetThis(argv);
168
169    if (!self->IsJSAPIHashSet()) {
170        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
171            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
172        } else {
173            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
174                                                                "The has method cannot be bound");
175            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
176        }
177    }
178    JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
179    JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
180    return jsHashSet->Has(thread, value.GetTaggedValue());
181}
182
183JSTaggedValue ContainersHashSet::Clear(EcmaRuntimeCallInfo *argv)
184{
185    ASSERT(argv != nullptr);
186    JSThread *thread = argv->GetThread();
187    BUILTINS_API_TRACE(thread, HashSet, Clear);
188    [[maybe_unused]] EcmaHandleScope handleScope(thread);
189    JSHandle<JSTaggedValue> self = GetThis(argv);
190
191    if (!self->IsJSAPIHashSet()) {
192        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
193            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
194        } else {
195            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
196                                                                "The clear method cannot be bound");
197            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
198        }
199    }
200    JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
201    jsHashSet->Clear(thread);
202    return JSTaggedValue::Undefined();
203}
204
205JSTaggedValue ContainersHashSet::GetLength(EcmaRuntimeCallInfo *argv)
206{
207    ASSERT(argv != nullptr);
208    JSThread *thread = argv->GetThread();
209    BUILTINS_API_TRACE(thread, HashSet, GetLength);
210    [[maybe_unused]] EcmaHandleScope handleScope(thread);
211    JSHandle<JSTaggedValue> self = GetThis(argv);
212
213    if (!self->IsJSAPIHashSet()) {
214        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
215            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
216        } else {
217            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
218                                                                "The getLength method cannot be bound");
219            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
220        }
221    }
222
223    JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
224    return jsHashSet->GetLength();
225}
226
227JSTaggedValue ContainersHashSet::IsEmpty(EcmaRuntimeCallInfo *argv)
228{
229    ASSERT(argv != nullptr);
230    JSThread *thread = argv->GetThread();
231    BUILTINS_API_TRACE(thread, HashSet, IsEmpty);
232    [[maybe_unused]] EcmaHandleScope handleScope(thread);
233    JSHandle<JSTaggedValue> self = GetThis(argv);
234
235    if (!self->IsJSAPIHashSet()) {
236        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIHashSet()) {
237            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
238        } else {
239            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
240                                                                "The isEmpty method cannot be bound");
241            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
242        }
243    }
244    JSHandle<JSAPIHashSet> jsHashSet = JSHandle<JSAPIHashSet>::Cast(self);
245    return jsHashSet->IsEmpty();
246}
247
248JSTaggedValue ContainersHashSet::ForEach(EcmaRuntimeCallInfo *argv)
249{
250    ASSERT(argv != nullptr);
251    JSThread *thread = argv->GetThread();
252    BUILTINS_API_TRACE(thread, HashSet, ForEach);
253    [[maybe_unused]] EcmaHandleScope handleScope(thread);
254    JSHandle<JSTaggedValue> thisHandle = GetThis(argv);
255    if (!thisHandle->IsJSAPIHashSet()) {
256        if (thisHandle->IsJSProxy() && JSHandle<JSProxy>::Cast(thisHandle)->GetTarget().IsJSAPIHashSet()) {
257            thisHandle = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(thisHandle)->GetTarget());
258        } else {
259            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
260                                                                "The forEach method cannot be bound");
261            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
262        }
263    }
264    JSHandle<JSTaggedValue> callbackFnHandle = GetCallArg(argv, 0);
265    if (!callbackFnHandle->IsCallable()) {
266        JSHandle<EcmaString> result = JSTaggedValue::ToString(thread, callbackFnHandle);
267        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
268        CString errorMsg =
269            "The type of \"callbackfn\" must be callable. Received value is: " + ConvertToString(*result);
270        JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str());
271        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
272    }
273    JSHandle<JSTaggedValue> thisArgHandle = GetCallArg(argv, 1);
274    JSHandle<JSAPIHashSet> hashSet = JSHandle<JSAPIHashSet>::Cast(thisHandle);
275    JSHandle<TaggedHashArray> table(thread, hashSet->GetTable());
276    uint32_t len = table->GetLength();
277    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
278    JSMutableHandle<TaggedQueue> queue(thread, factory->NewTaggedQueue(0));
279    JSMutableHandle<TaggedNode> node(thread, JSTaggedValue::Undefined());
280    JSMutableHandle<JSTaggedValue> currentKey(thread, JSTaggedValue::Undefined());
281    uint32_t index = 0;
282    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
283    while (index < len) {
284        node.Update(TaggedHashArray::GetCurrentNode(thread, queue, table, index));
285        if (!node.GetTaggedValue().IsHole()) {
286            currentKey.Update(node->GetKey());
287            EcmaRuntimeCallInfo *info =
288                EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle,
289                                                    thisArgHandle, undefined, 3); // 3: three args
290            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
291            info->SetCallArg(currentKey.GetTaggedValue(), currentKey.GetTaggedValue(), thisHandle.GetTaggedValue());
292            JSTaggedValue funcResult = JSFunction::Call(info);
293            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult);
294        }
295    }
296    return JSTaggedValue::Undefined();
297}
298} // namespace panda::ecmascript::containers
299