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