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 "ecmascript/containers/containers_queue.h"
17
18#include "ecmascript/containers/containers_errors.h"
19#include "ecmascript/interpreter/interpreter.h"
20#include "ecmascript/js_api/js_api_queue.h"
21#include "ecmascript/js_function.h"
22
23namespace panda::ecmascript::containers {
24JSTaggedValue ContainersQueue::QueueConstructor(EcmaRuntimeCallInfo *argv)
25{
26    ASSERT(argv);
27    BUILTINS_API_TRACE(argv->GetThread(), Queue, Constructor);
28    JSThread *thread = argv->GetThread();
29    [[maybe_unused]] EcmaHandleScope handleScope(thread);
30    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
31    JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
32    if (newTarget->IsUndefined()) {
33        JSTaggedValue error =
34            ContainerError::BusinessError(thread, ErrorFlag::IS_NULL_ERROR,
35                                          "The Queue's constructor cannot be directly invoked");
36        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
37    }
38    JSHandle<JSTaggedValue> constructor = GetConstructor(argv);
39    JSHandle<JSObject> obj = factory->NewJSObjectByConstructor(JSHandle<JSFunction>(constructor), newTarget);
40    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
41    JSHandle<TaggedArray> newTaggedArray = factory->NewTaggedArray(JSAPIQueue::DEFAULT_CAPACITY_LENGTH);
42    obj->SetElements(thread, newTaggedArray);
43
44    return obj.GetTaggedValue();
45}
46
47JSTaggedValue ContainersQueue::Add(EcmaRuntimeCallInfo *argv)
48{
49    ASSERT(argv);
50    BUILTINS_API_TRACE(argv->GetThread(), Queue, Add);
51    JSThread *thread = argv->GetThread();
52    [[maybe_unused]] EcmaHandleScope handleScope(thread);
53    JSHandle<JSTaggedValue> self = GetThis(argv);
54
55    if (!self->IsJSAPIQueue()) {
56        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIQueue()) {
57            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
58        } else {
59            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
60                                                                "The add method cannot be bound");
61            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
62        }
63    }
64
65    JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
66    JSAPIQueue::Add(thread, JSHandle<JSAPIQueue>::Cast(self), value);
67    return JSTaggedValue::True();
68}
69
70JSTaggedValue ContainersQueue::GetFirst(EcmaRuntimeCallInfo *argv)
71{
72    ASSERT(argv);
73    BUILTINS_API_TRACE(argv->GetThread(), Queue, Add);
74    JSThread *thread = argv->GetThread();
75    [[maybe_unused]] EcmaHandleScope handleScope(thread);
76    JSHandle<JSTaggedValue> self = GetThis(argv);
77
78    if (!self->IsJSAPIQueue()) {
79        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIQueue()) {
80            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
81        } else {
82            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
83                                                                "The getFirst method cannot be bound");
84            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
85        }
86    }
87
88    JSTaggedValue value = JSAPIQueue::GetFirst(thread, JSHandle<JSAPIQueue>::Cast(self));
89    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::False());
90    return value;
91}
92
93JSTaggedValue ContainersQueue::Pop(EcmaRuntimeCallInfo *argv)
94{
95    ASSERT(argv);
96    BUILTINS_API_TRACE(argv->GetThread(), Queue, Add);
97    JSThread *thread = argv->GetThread();
98    [[maybe_unused]] EcmaHandleScope handleScope(thread);
99    JSHandle<JSTaggedValue> self = GetThis(argv);
100
101    if (!self->IsJSAPIQueue()) {
102        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIQueue()) {
103            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
104        } else {
105            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
106                                                                "The pop method cannot be bound");
107            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
108        }
109    }
110
111    JSTaggedValue value = JSAPIQueue::Pop(thread, JSHandle<JSAPIQueue>::Cast(self));
112    RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::False());
113    return value;
114}
115
116JSTaggedValue ContainersQueue::ForEach(EcmaRuntimeCallInfo *argv)
117{
118    ASSERT(argv);
119    BUILTINS_API_TRACE(argv->GetThread(), Queue, ForEach);
120    JSThread *thread = argv->GetThread();
121    [[maybe_unused]] EcmaHandleScope handleScope(thread);
122
123    // Let O be ToObject(this value).
124    JSHandle<JSTaggedValue> thisHandle = GetThis(argv); // JSAPIQueue
125    if (!thisHandle->IsJSAPIQueue()) {
126        if (thisHandle->IsJSProxy() && JSHandle<JSProxy>::Cast(thisHandle)->GetTarget().IsJSAPIQueue()) {
127            thisHandle = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(thisHandle)->GetTarget());
128        } else {
129            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
130                                                                "The forEach method cannot be bound");
131            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
132        }
133    }
134
135    JSHandle<JSAPIQueue> queue = JSHandle<JSAPIQueue>::Cast(thisHandle);
136    // Let len be ToLength(Get(O, "length")).
137    uint32_t len = JSAPIQueue::GetArrayLength(thread, queue);
138    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
139
140    JSHandle<JSTaggedValue> callbackFnHandle = GetCallArg(argv, 0);
141    // If IsCallable(callbackfn) is false, throw a TypeError exception.
142    if (!callbackFnHandle->IsCallable()) {
143        JSHandle<EcmaString> result = JSTaggedValue::ToString(thread, callbackFnHandle.GetTaggedValue());
144        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
145        CString errorMsg =
146            "The type of \"callbackfn\" must be callable. Received value is: " + ConvertToString(*result);
147        JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str());
148        THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
149    }
150    // If thisArg was supplied, let T be thisArg; else let T be undefined.
151    JSHandle<JSTaggedValue> thisArgHandle = GetCallArg(argv, 1);
152
153    JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
154    uint32_t index = 0;
155    uint32_t k = 0;
156    JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
157    while (k < len) {
158        JSHandle<JSTaggedValue> kValue =
159            JSHandle<JSTaggedValue>(thread, queue->Get(thread, index));
160        index = queue->GetNextPosition(index);
161        key.Update(JSTaggedValue(k));
162        const uint32_t argsLength = 3;
163        EcmaRuntimeCallInfo *info =
164            EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
165        RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
166        info->SetCallArg(kValue.GetTaggedValue(), key.GetTaggedValue(), thisHandle.GetTaggedValue());
167        JSTaggedValue funcResult = JSFunction::Call(info);
168        RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult);
169        k++;
170    }
171    return JSTaggedValue::Undefined();
172}
173
174JSTaggedValue ContainersQueue::GetIteratorObj(EcmaRuntimeCallInfo *argv)
175{
176    ASSERT(argv);
177    BUILTINS_API_TRACE(argv->GetThread(), Queue, GetIteratorObj);
178    JSThread *thread = argv->GetThread();
179    [[maybe_unused]] EcmaHandleScope handleScope(thread);
180    ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
181    JSHandle<JSTaggedValue> self = GetThis(argv);
182    if (!self->IsJSAPIQueue()) {
183        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIQueue()) {
184            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
185        } else {
186            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
187                                                                "The Symbol.iterator method cannot be bound");
188            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
189        }
190    }
191    JSHandle<JSAPIQueueIterator> iter(factory->NewJSAPIQueueIterator(JSHandle<JSAPIQueue>::Cast(self)));
192    return iter.GetTaggedValue();
193}
194
195JSTaggedValue ContainersQueue::GetSize(EcmaRuntimeCallInfo *argv)
196{
197    ASSERT(argv);
198    BUILTINS_API_TRACE(argv->GetThread(), Queue, GetSize);
199    JSThread *thread = argv->GetThread();
200    [[maybe_unused]] EcmaHandleScope handleScope(thread);
201    JSHandle<JSTaggedValue> self = GetThis(argv);
202
203    if (!self->IsJSAPIQueue()) {
204        if (self->IsJSProxy() && JSHandle<JSProxy>::Cast(self)->GetTarget().IsJSAPIQueue()) {
205            self = JSHandle<JSTaggedValue>(thread, JSHandle<JSProxy>::Cast(self)->GetTarget());
206        } else {
207            JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR,
208                                                                "The getLength method cannot be bound");
209            THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception());
210        }
211    }
212
213    uint32_t length = JSHandle<JSAPIQueue>::Cast(self)->GetSize();
214    RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
215
216    return JSTaggedValue(length);
217}
218} // namespace panda::ecmascript::containers
219