/* * Copyright (c) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "containers_hashmap.h" #include "ecmascript/containers/containers_errors.h" #include "ecmascript/ecma_vm.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_api/js_api_hashmap.h" #include "ecmascript/js_api/js_api_hashmap_iterator.h" #include "ecmascript/js_function.h" #include "ecmascript/object_factory.h" #include "ecmascript/tagged_array-inl.h" #include "ecmascript/tagged_hash_array.h" #include "ecmascript/tagged_node.h" #include "ecmascript/tagged_queue.h" namespace panda::ecmascript::containers { JSTaggedValue ContainersHashMap::HashMapConstructor(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Constructor); [[maybe_unused]] EcmaHandleScope handleScope(thread); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle newTarget = GetNewTarget(argv); if (newTarget->IsUndefined()) { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::IS_NULL_ERROR, "The HashMap's constructor cannot be directly invoked"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } JSHandle constructor = GetConstructor(argv); JSHandle obj = factory->NewJSObjectByConstructor(JSHandle(constructor), newTarget); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); JSHandle hashMap = JSHandle::Cast(obj); JSTaggedValue hashMapArray = TaggedHashArray::Create(thread); hashMap->SetTable(thread, hashMapArray); hashMap->SetSize(0); return hashMap.GetTaggedValue(); } JSTaggedValue ContainersHashMap::Keys(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Keys); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The keys method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle iter = JSAPIHashMapIterator::CreateHashMapIterator(thread, self, IterationKind::KEY); return iter.GetTaggedValue(); } JSTaggedValue ContainersHashMap::Values(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Values); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The values method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle iter = JSAPIHashMapIterator::CreateHashMapIterator(thread, self, IterationKind::VALUE); return iter.GetTaggedValue(); } JSTaggedValue ContainersHashMap::Entries(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Entries); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The entries method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle iter = JSAPIHashMapIterator::CreateHashMapIterator(thread, self, IterationKind::KEY_AND_VALUE); return iter.GetTaggedValue(); } JSTaggedValue ContainersHashMap::ForEach(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, ForEach); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle thisHandle = GetThis(argv); if (!thisHandle->IsJSAPIHashMap()) { if (thisHandle->IsJSProxy() && JSHandle::Cast(thisHandle)->GetTarget().IsJSAPIHashMap()) { thisHandle = JSHandle(thread, JSHandle::Cast(thisHandle)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The forEach method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle callbackFnHandle = GetCallArg(argv, 0); if (!callbackFnHandle->IsCallable()) { JSHandle result = JSTaggedValue::ToString(thread, callbackFnHandle); CString errorMsg = "The type of \"callbackfn\" must be callable. Received value is: " + ConvertToString(*result); JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } JSHandle thisArgHandle = GetCallArg(argv, 1); JSHandle hashMap = JSHandle::Cast(thisHandle); JSHandle table(thread, hashMap->GetTable()); uint32_t len = table->GetLength(); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSMutableHandle queue(thread, factory->NewTaggedQueue(0)); JSMutableHandle node(thread, JSTaggedValue::Undefined()); JSMutableHandle key(thread, JSTaggedValue::Undefined()); JSMutableHandle value(thread, JSTaggedValue::Undefined()); uint32_t index = 0; JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); while (index < len) { node.Update(TaggedHashArray::GetCurrentNode(thread, queue, table, index)); if (!node.GetTaggedValue().IsHole()) { key.Update(node->GetKey()); value.Update(node->GetValue()); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, 3); // 3: three args RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); info->SetCallArg(value.GetTaggedValue(), key.GetTaggedValue(), thisHandle.GetTaggedValue()); JSTaggedValue funcResult = JSFunction::Call(info); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult); } } return JSTaggedValue::Undefined(); } JSTaggedValue ContainersHashMap::Set(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Set); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The set method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle key = GetCallArg(argv, 0); JSHandle value = GetCallArg(argv, 1); JSHandle hashMap = JSHandle::Cast(self); JSAPIHashMap::Set(thread, hashMap, key, value); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); return hashMap.GetTaggedValue(); } JSTaggedValue ContainersHashMap::SetAll(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, SetAll); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The setAll method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle obj = GetCallArg(argv, 0); if (!obj->IsJSAPIHashMap()) { if (obj->IsJSProxy() && JSHandle::Cast(obj)->GetTarget().IsJSAPIHashMap()) { obj = JSHandle(thread, JSHandle::Cast(obj)->GetTarget()); } else { JSHandle result = JSTaggedValue::ToString(thread, obj); CString errorMsg = "The type of \"map\" must be HashMap. Received value is: " + ConvertToString(*result); JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::TYPE_ERROR, errorMsg.c_str()); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle targetMap = JSHandle::Cast(self); JSHandle sourceMap = JSHandle::Cast(obj); JSAPIHashMap::SetAll(thread, targetMap, sourceMap); return self.GetTaggedValue(); } JSTaggedValue ContainersHashMap::Get(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Get); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The get method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle key = GetCallArg(argv, 0); JSHandle hashMap = JSHandle::Cast(self); return hashMap->Get(thread, key.GetTaggedValue()); } JSTaggedValue ContainersHashMap::Remove(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Remove); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The remove method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle key = GetCallArg(argv, 0); JSHandle hashMap = JSHandle::Cast(self); return JSAPIHashMap::Remove(thread, hashMap, key.GetTaggedValue()); } JSTaggedValue ContainersHashMap::HasKey(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, HasKey); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The hasKey method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle key = GetCallArg(argv, 0); JSHandle hashMap = JSHandle::Cast(self); return hashMap->HasKey(thread, key.GetTaggedValue()); } JSTaggedValue ContainersHashMap::HasValue(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, HasValue); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The hasValue method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle value = GetCallArg(argv, 0); JSHandle hashMap = JSHandle::Cast(self); return JSAPIHashMap::HasValue(thread, hashMap, value); } JSTaggedValue ContainersHashMap::Replace(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Replace); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The replace method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle key = GetCallArg(argv, 0); JSHandle newValue = GetCallArg(argv, 1); JSHandle jsHashMap = JSHandle::Cast(self); return jsHashMap->Replace(thread, key.GetTaggedValue(), newValue.GetTaggedValue()); } JSTaggedValue ContainersHashMap::Clear(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, Clear); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The clear method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle jsHashMap = JSHandle::Cast(self); jsHashMap->Clear(thread); return JSTaggedValue::Undefined(); } JSTaggedValue ContainersHashMap::GetLength(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, GetLength); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The getLength method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle jsHashMap = JSHandle::Cast(self); return jsHashMap->GetLength(); } JSTaggedValue ContainersHashMap::IsEmpty(EcmaRuntimeCallInfo *argv) { ASSERT(argv != nullptr); JSThread *thread = argv->GetThread(); BUILTINS_API_TRACE(thread, HashMap, IsEmpty); [[maybe_unused]] EcmaHandleScope handleScope(thread); JSHandle self = GetThis(argv); if (!self->IsJSAPIHashMap()) { if (self->IsJSProxy() && JSHandle::Cast(self)->GetTarget().IsJSAPIHashMap()) { self = JSHandle(thread, JSHandle::Cast(self)->GetTarget()); } else { JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::BIND_ERROR, "The isEmpty method cannot be bound"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, error, JSTaggedValue::Exception()); } } JSHandle jsHashMap = JSHandle::Cast(self); return jsHashMap->IsEmpty(); } } // namespace panda::ecmascript::containers