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_map.h"
17
18#include "ecmascript/interpreter/interpreter.h"
19#include "ecmascript/js_function.h"
20#include "ecmascript/linked_hash_table.h"
21#include "ecmascript/shared_objects/concurrent_api_scope.h"
22#include "ecmascript/shared_objects/js_shared_map.h"
23#include "ecmascript/shared_objects/js_shared_map_iterator.h"
24
25namespace panda::ecmascript::builtins {
26JSTaggedValue BuiltinsSharedMap::Constructor(EcmaRuntimeCallInfo *argv)
27{
28    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Constructor);
29    JSThread *thread = argv->GetThread();
30    [[maybe_unused]] EcmaHandleScope handleScope(thread);
31    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
32    // 1. If NewTarget is undefined, throw exception
33    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
34    if (newTarget->IsUndefined()) {
35        JSTaggedValue error = containers::ContainerError::BusinessError(
36            thread, containers::ErrorFlag::IS_NULL_ERROR, "The ArkTS Map's constructor cannot be directly invoked.");
37        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
38    }
39    // 2.Let Map be OrdinaryCreateFromConstructor(NewTarget, "%MapPrototype%", «‍[[MapData]]» ).
40    JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
41    ASSERT(constructor->IsJSSharedFunction() && constructor.GetTaggedValue().IsInSharedHeap());
42    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
43    // 3.returnIfAbrupt()
44    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
45    ASSERT(obj.GetTaggedValue().IsInSharedHeap());
46    JSHandle<JSSharedMap> map = JSHandle<JSSharedMap>::Cast(obj);
47
48    // 4.Set map’s [[MapData]] internal slot to a new empty List.
49    JSHandle<LinkedHashMap> linkedMap = LinkedHashMap::Create(thread,
50        LinkedHashMap::MIN_CAPACITY, MemSpaceKind::SHARED);
51    map->SetLinkedMap(thread, linkedMap);
52    // add data into set from iterable
53    // 5.If iterable is not present, let iterable be undefined.
54    // 6.If iterable is either undefined or null, let iter be undefined.
55    JSHandle<JSTaggedValue> iterable = GetCallArg(argv, 0);
56    if (iterable->IsUndefined() || iterable->IsNull()) {
57        return map.GetTaggedValue();
58    }
59    if (!iterable->IsECMAObject()) {
60        THROW_TYPE_ERROR_AND_RETURN(thread, "iterable is not object", JSTaggedValue::Exception());
61    }
62    // Let adder be Get(map, "set").
63    JSHandle<JSTaggedValue> adderKey = thread->GlobalConstants()->GetHandledSetString();
64    JSHandle<JSTaggedValue> adder = JSObject::GetProperty(thread, JSHandle<JSTaggedValue>(map), adderKey).GetValue();
65    // ReturnIfAbrupt(adder).
66    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, adder.GetTaggedValue());
67    return AddEntriesFromIterable(thread, obj, iterable, adder, factory);
68}
69
70JSTaggedValue BuiltinsSharedMap::Set(EcmaRuntimeCallInfo *argv)
71{
72    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Set);
73    JSThread *thread = argv->GetThread();
74    [[maybe_unused]] EcmaHandleScope handleScope(thread);
75    JSHandle<JSTaggedValue> self = GetThis(argv);
76    if (!self->IsJSSharedMap()) {
77        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
78                                                               "The set method cannot be bound.");
79        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
80    }
81    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
82    JSHandle<JSTaggedValue> value = GetCallArg(argv, 1);
83    JSHandle<JSSharedMap> map(self);
84    JSSharedMap::Set(thread, map, key, value);
85    return map.GetTaggedValue();
86}
87
88JSTaggedValue BuiltinsSharedMap::Clear(EcmaRuntimeCallInfo *argv)
89{
90    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Clear);
91    JSThread *thread = argv->GetThread();
92    [[maybe_unused]] EcmaHandleScope handleScope(thread);
93    JSHandle<JSTaggedValue> self = GetThis(argv);
94    if (!self->IsJSSharedMap()) {
95        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
96                                                               "The clear method cannot be bound.");
97        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
98    }
99    JSHandle<JSSharedMap> map(self);
100    JSSharedMap::Clear(thread, map);
101    return JSTaggedValue::Undefined();
102}
103
104JSTaggedValue BuiltinsSharedMap::Delete(EcmaRuntimeCallInfo *argv)
105{
106    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Delete);
107    JSThread *thread = argv->GetThread();
108    [[maybe_unused]] EcmaHandleScope handleScope(thread);
109    JSHandle<JSTaggedValue> self = GetThis(argv);
110    if (!self->IsJSSharedMap()) {
111        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
112                                                               "The delete method cannot be bound.");
113        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
114    }
115    JSHandle<JSSharedMap> map(self);
116    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
117    bool flag = JSSharedMap::Delete(thread, map, key);
118    return GetTaggedBoolean(flag);
119}
120
121JSTaggedValue BuiltinsSharedMap::Has(EcmaRuntimeCallInfo *argv)
122{
123    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Has);
124    JSThread *thread = argv->GetThread();
125    [[maybe_unused]] EcmaHandleScope handleScope(thread);
126    JSHandle<JSTaggedValue> self(GetThis(argv));
127    if (!self->IsJSSharedMap()) {
128        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
129                                                               "The has method cannot be bound.");
130        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
131    }
132    JSHandle<JSSharedMap> map(self);
133    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
134    bool flag = JSSharedMap::Has(thread, map, key.GetTaggedValue());
135    return GetTaggedBoolean(flag);
136}
137
138JSTaggedValue BuiltinsSharedMap::Get(EcmaRuntimeCallInfo *argv)
139{
140    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Get);
141    JSThread *thread = argv->GetThread();
142    [[maybe_unused]] EcmaHandleScope handleScope(thread);
143    JSHandle<JSTaggedValue> self(GetThis(argv));
144    if (!self->IsJSSharedMap()) {
145        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
146                                                               "The get method cannot be bound.");
147        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
148    }
149    JSHandle<JSSharedMap> map(self);
150    JSHandle<JSTaggedValue> key = GetCallArg(argv, 0);
151    JSTaggedValue value = JSSharedMap::Get(thread, map, key.GetTaggedValue());
152    return value;
153}
154
155JSTaggedValue BuiltinsSharedMap::ForEach(EcmaRuntimeCallInfo *argv)
156{
157    JSThread *thread = argv->GetThread();
158    BUILTINS_API_TRACE(thread, SharedMap, ForEach);
159    [[maybe_unused]] EcmaHandleScope handleScope(thread);
160    JSHandle<JSTaggedValue> self = GetThis(argv);
161    if (!self->IsJSSharedMap()) {
162        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
163                                                               "The forEach method cannot be bound.");
164        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
165    }
166    [[maybe_unused]] ConcurrentApiScope<JSSharedMap> scope(thread, self);
167    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
168    JSHandle<JSSharedMap> map(self);
169    JSHandle<JSTaggedValue> func(GetCallArg(argv, 0));
170    if (!func->IsCallable()) {
171        THROW_TYPE_ERROR_AND_RETURN(thread, "obj is not Callable", JSTaggedValue::Exception());
172    }
173    JSHandle<JSTaggedValue> thisArg = GetCallArg(argv, 1);
174    JSMutableHandle<LinkedHashMap> hashMap(thread, map->GetLinkedMap());
175    const uint32_t argsLength = 3;
176    int index = 0;
177    int totalElements = hashMap->NumberOfElements() + hashMap->NumberOfDeletedElements();
178    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
179    // Repeat for each e that is an element of entries, in original insertion order
180    while (index < totalElements) {
181        JSHandle<JSTaggedValue> key(thread, hashMap->GetKey(index++));
182        if (!key->IsHole()) {
183            JSHandle<JSTaggedValue> value(thread, hashMap->GetValue(index - 1));
184            EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(
185                thread, func, thisArg, undefined, argsLength);
186            RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
187            info->SetCallArg(value.GetTaggedValue(), key.GetTaggedValue(), map.GetTaggedValue());
188            JSTaggedValue ret = JSFunction::Call(info);
189            RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, ret);
190        }
191    }
192
193    return JSTaggedValue::Undefined();
194}
195
196JSTaggedValue BuiltinsSharedMap::Species(EcmaRuntimeCallInfo *argv)
197{
198    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Species);
199    return GetThis(argv).GetTaggedValue();
200}
201
202JSTaggedValue BuiltinsSharedMap::GetSize(EcmaRuntimeCallInfo *argv)
203{
204    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, GetSize);
205    JSThread *thread = argv->GetThread();
206    [[maybe_unused]] EcmaHandleScope handleScope(thread);
207    JSHandle<JSTaggedValue> self(GetThis(argv));
208    if (!self->IsJSSharedMap()) {
209        THROW_TYPE_ERROR_AND_RETURN(thread, "obj is not SharedMap", JSTaggedValue::Exception());
210    }
211    JSHandle<JSSharedMap> map(self);
212    uint32_t size = JSSharedMap::GetSize(thread, map);
213    return JSTaggedValue(size);
214}
215
216JSTaggedValue BuiltinsSharedMap::Entries(EcmaRuntimeCallInfo *argv)
217{
218    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Entries);
219    JSThread *thread = argv->GetThread();
220    [[maybe_unused]] EcmaHandleScope handleScope(thread);
221    JSHandle<JSTaggedValue> self = GetThis(argv);
222    if (!self->IsJSSharedMap()) {
223        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
224                                                               "The entries method cannot be bound.");
225        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
226    }
227    JSHandle<JSTaggedValue> iter = JSSharedMapIterator::CreateMapIterator(thread, self, IterationKind::KEY_AND_VALUE);
228    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
229    return iter.GetTaggedValue();
230}
231
232JSTaggedValue BuiltinsSharedMap::Keys(EcmaRuntimeCallInfo *argv)
233{
234    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Keys);
235    JSThread *thread = argv->GetThread();
236    [[maybe_unused]] EcmaHandleScope handleScope(thread);
237    JSHandle<JSTaggedValue> self = GetThis(argv);
238    if (!self->IsJSSharedMap()) {
239        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
240                                                               "The keys method cannot be bound.");
241        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
242    }
243    JSHandle<JSTaggedValue> iter = JSSharedMapIterator::CreateMapIterator(thread, self, IterationKind::KEY);
244    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
245    return iter.GetTaggedValue();
246}
247
248JSTaggedValue BuiltinsSharedMap::Values(EcmaRuntimeCallInfo *argv)
249{
250    BUILTINS_API_TRACE(argv->GetThread(), SharedMap, Values);
251    JSThread *thread = argv->GetThread();
252    [[maybe_unused]] EcmaHandleScope handleScope(thread);
253    JSHandle<JSTaggedValue> self = GetThis(argv);
254    if (!self->IsJSSharedMap()) {
255        auto error = containers::ContainerError::BusinessError(thread, containers::ErrorFlag::BIND_ERROR,
256                                                               "The values method cannot be bound.");
257        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
258    }
259    JSHandle<JSTaggedValue> iter = JSSharedMapIterator::CreateMapIterator(thread, self, IterationKind::VALUE);
260    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
261    return iter.GetTaggedValue();
262}
263
264JSTaggedValue BuiltinsSharedMap::AddEntriesFromIterable(JSThread *thread, const JSHandle<JSObject> &target,
265    const JSHandle<JSTaggedValue> &iterable, const JSHandle<JSTaggedValue> &adder, ObjectFactory *factory)
266{
267    BUILTINS_API_TRACE(thread, SharedMap, AddEntriesFromIterable);
268    // If IsCallable(adder) is false, throw a TypeError exception
269    if (!adder->IsCallable()) {
270        THROW_TYPE_ERROR_AND_RETURN(thread, "adder is not callable", adder.GetTaggedValue());
271    }
272    // Let iter be GetIterator(iterable).
273    JSHandle<JSTaggedValue> iter(JSIterator::GetIterator(thread, iterable));
274    // ReturnIfAbrupt(iter).
275    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, iter.GetTaggedValue());
276    JSHandle<JSTaggedValue> keyIndex(thread, JSTaggedValue(0));
277    JSHandle<JSTaggedValue> valueIndex(thread, JSTaggedValue(1));
278    JSHandle<JSTaggedValue> next = JSIterator::IteratorStep(thread, iter);
279    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
280    while (!next->IsFalse()) {
281        // Let nextValue be IteratorValue(next).
282        JSHandle<JSTaggedValue> nextValue(JSIterator::IteratorValue(thread, next));
283        // ReturnIfAbrupt(nextValue).
284        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
285
286        // If Type(nextItem) is not Object
287        if (!nextValue->IsECMAObject()) {
288            JSHandle<JSObject> typeError = factory->GetJSError(ErrorType::TYPE_ERROR,
289                                                               "nextItem is not Object", StackCheck::NO);
290            JSHandle<JSTaggedValue> record(
291                factory->NewCompletionRecord(CompletionRecordType::THROW, JSHandle<JSTaggedValue>(typeError)));
292            JSTaggedValue ret = JSIterator::IteratorClose(thread, iter, record).GetTaggedValue();
293            if (!thread->HasPendingException()) {
294                THROW_NEW_ERROR_AND_RETURN_VALUE(thread, typeError.GetTaggedValue(), ret);
295            }
296            return ret;
297        }
298        // Let k be Get(nextItem, "0").
299        JSHandle<JSTaggedValue> key = JSTaggedValue::GetProperty(thread, nextValue, keyIndex).GetValue();
300        // If k is an abrupt completion, return IteratorClose(iter, k).
301        if (thread->HasPendingException()) {
302            return JSIterator::IteratorCloseAndReturn(thread, iter);
303        }
304        // Let v be Get(nextItem, "1").
305        JSHandle<JSTaggedValue> value = JSTaggedValue::GetProperty(thread, nextValue, valueIndex).GetValue();
306        // If v is an abrupt completion, return IteratorClose(iter, v).
307        if (thread->HasPendingException()) {
308            return JSIterator::IteratorCloseAndReturn(thread, iter);
309        }
310        const uint32_t argsLength = 2;  // 2: key and value pair
311        JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
312        EcmaRuntimeCallInfo *info =
313            EcmaInterpreter::NewRuntimeCallInfo(thread, adder, JSHandle<JSTaggedValue>(target), undefined, argsLength);
314        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
315        info->SetCallArg(key.GetTaggedValue(), value.GetTaggedValue());
316        JSFunction::Call(info);
317        // If status is an abrupt completion, return IteratorClose(iter, status).
318        if (thread->HasPendingException()) {
319            return JSIterator::IteratorCloseAndReturn(thread, iter);
320        }
321        // Let next be IteratorStep(iter).
322        next = JSIterator::IteratorStep(thread, iter);
323        // ReturnIfAbrupt(next).
324        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, next.GetTaggedValue());
325    }
326    return target.GetTaggedValue();
327}
328}  // namespace panda::ecmascript::builtins
329