arkcompiler_ets_runtime/ecmascript/builtins/builtins_string.cpp
xiaoweidong 3bf89635ca Opt array.from & splice
Issue:https://gitee.com/openharmony/arkcompiler_ets_runtime/issues/I8K9KP

Change-Id: I12da92453e9c72d33334a31107fc7280fa218895
Signed-off-by: xiaoweidong <xiaoweidong@huawei.com>
2023-11-30 15:36:17 +08:00

2203 lines
102 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021 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 "ecmascript/builtins/builtins_string.h"
#include <algorithm>
#include <vector>
#include <map>
#include "ecmascript/intl/locale_helper.h"
#include "ecmascript/base/number_helper.h"
#include "ecmascript/base/string_helper.h"
#include "ecmascript/builtins/builtins_json.h"
#include "ecmascript/builtins/builtins_regexp.h"
#include "ecmascript/builtins/builtins_symbol.h"
#include "ecmascript/ecma_runtime_call_info.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/ecma_vm.h"
#include "ecmascript/global_env.h"
#include "ecmascript/interpreter/fast_runtime_stub-inl.h"
#include "ecmascript/interpreter/interpreter.h"
#include "ecmascript/js_array.h"
#include "ecmascript/js_hclass.h"
#include "ecmascript/js_object-inl.h"
#include "ecmascript/js_primitive_ref.h"
#include "ecmascript/js_regexp.h"
#include "ecmascript/js_string_iterator.h"
#include "ecmascript/js_tagged_value-inl.h"
#include "ecmascript/mem/c_containers.h"
#include "ecmascript/object_factory.h"
#include "ecmascript/property_detector-inl.h"
#include "ecmascript/tagged_array-inl.h"
#include "ecmascript/tagged_array.h"
#ifdef ARK_SUPPORT_INTL
#include "ecmascript/js_collator.h"
#include "ecmascript/js_locale.h"
#else
#ifndef ARK_NOT_SUPPORT_INTL_GLOBAL
#include "ecmascript/intl/global_intl_helper.h"
#endif
#endif
#include "unicode/normalizer2.h"
#include "unicode/normlzr.h"
#include "unicode/unistr.h"
namespace panda::ecmascript::builtins {
using ObjectFactory = ecmascript::ObjectFactory;
using JSArray = ecmascript::JSArray;
// 21.1.1.1 String(value)
JSTaggedValue BuiltinsString::StringConstructor(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Constructor);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
if (argv->GetArgsNumber() > 0) {
JSHandle<JSTaggedValue> valTagNew = GetCallArg(argv, 0);
if (newTarget->IsUndefined() && valTagNew->IsSymbol()) {
return BuiltinsSymbol::SymbolDescriptiveString(thread, valTagNew.GetTaggedValue());
}
JSHandle<EcmaString> str = JSTaggedValue::ToString(thread, valTagNew);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (newTarget->IsUndefined()) {
return str.GetTaggedValue();
}
JSHandle<JSTaggedValue> strTag(str);
return JSPrimitiveRef::StringCreate(thread, strTag, newTarget).GetTaggedValue();
}
JSHandle<EcmaString> val = factory->GetEmptyString();
JSHandle<JSTaggedValue> valTag(val);
if (newTarget->IsUndefined()) {
return factory->GetEmptyString().GetTaggedValue();
}
return JSPrimitiveRef::StringCreate(thread, valTag, newTarget).GetTaggedValue();
}
// 21.1.2.1
JSTaggedValue BuiltinsString::FromCharCode(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, FromCharCode);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
uint32_t argLength = argv->GetArgsNumber();
if (argLength == 0) {
return factory->GetEmptyString().GetTaggedValue();
}
if (argLength == 1) {
JSHandle<JSTaggedValue> codePointTag = BuiltinsString::GetCallArg(argv, 0);
uint16_t codePointValue = JSTaggedValue::ToUint16(thread, codePointTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> strHandle = factory->NewFromUtf16Literal(&codePointValue, 1);
return strHandle.GetTaggedValue();
}
CVector<uint16_t> valueTable;
valueTable.reserve(argLength);
for (uint32_t i = 0; i < argLength; i++) {
JSHandle<JSTaggedValue> nextCp = BuiltinsString::GetCallArg(argv, i);
uint16_t nextCv = JSTaggedValue::ToUint16(thread, nextCp);
valueTable.emplace_back(nextCv);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
return factory->NewFromUtf16Literal(valueTable.data(), valueTable.size()).GetTaggedValue();
}
// 21.1.2.2
JSTaggedValue BuiltinsString::FromCodePoint(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, FromCodePoint);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
uint32_t argLength = argv->GetArgsNumber();
if (argLength == 0) {
return factory->GetEmptyString().GetTaggedValue();
}
std::u16string u16str;
uint32_t u16strSize = argLength;
for (uint32_t i = 0; i < argLength; i++) {
JSHandle<JSTaggedValue> nextCpTag = BuiltinsString::GetCallArg(argv, i);
JSTaggedNumber nextCpVal = JSTaggedValue::ToNumber(thread, nextCpTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!nextCpVal.IsInteger()) {
THROW_RANGE_ERROR_AND_RETURN(thread, "is not integer", JSTaggedValue::Exception());
}
int32_t cp = nextCpVal.ToInt32();
if (cp < 0 || cp > ENCODE_MAX_UTF16) {
THROW_RANGE_ERROR_AND_RETURN(thread, "CodePoint < 0 or CodePoint > 0x10FFFF", JSTaggedValue::Exception());
}
if (cp == 0) {
CVector<uint16_t> data {0x00};
return factory->NewFromUtf16Literal(data.data(), 1).GetTaggedValue();
}
if (cp > UINT16_MAX) {
uint16_t cu1 =
std::floor((static_cast<uint32_t>(cp) - ENCODE_SECOND_FACTOR) / ENCODE_FIRST_FACTOR) + ENCODE_LEAD_LOW;
uint16_t cu2 =
((static_cast<uint32_t>(cp) - ENCODE_SECOND_FACTOR) % ENCODE_FIRST_FACTOR) + ENCODE_TRAIL_LOW;
std::u16string nextU16str1 = base::StringHelper::Utf16ToU16String(&cu1, 1);
std::u16string nextU16str2 = base::StringHelper::Utf16ToU16String(&cu2, 1);
base::StringHelper::InplaceAppend(u16str, nextU16str1);
base::StringHelper::InplaceAppend(u16str, nextU16str2);
u16strSize++;
} else {
auto u16tCp = static_cast<uint16_t>(cp);
std::u16string nextU16str = base::StringHelper::Utf16ToU16String(&u16tCp, 1);
base::StringHelper::InplaceAppend(u16str, nextU16str);
}
}
const char16_t *constChar16tData = u16str.data();
auto *char16tData = const_cast<char16_t *>(constChar16tData);
auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue();
}
// 21.1.2.4
JSTaggedValue BuiltinsString::Raw(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Raw);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
// Let cooked be ToObject(template).
JSHandle<JSObject> cooked = JSTaggedValue::ToObject(thread, BuiltinsString::GetCallArg(argv, 0));
// ReturnIfAbrupt(cooked).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let raw be ToObject(Get(cooked, "raw")).
JSHandle<JSTaggedValue> rawKey(factory->NewFromASCII("raw"));
JSHandle<JSTaggedValue> rawTag =
JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(cooked), rawKey).GetValue();
JSHandle<JSObject> rawObj = JSTaggedValue::ToObject(thread, rawTag);
// ReturnIfAbrupt(rawObj).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
JSHandle<JSTaggedValue> rawLen =
JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(rawObj), lengthKey).GetValue();
// ReturnIfAbrupt(rawLen).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedNumber lengthNumber = JSTaggedValue::ToLength(thread, rawLen);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int length = static_cast<int>(lengthNumber.ToUint32());
if (length <= 0) {
return factory->GetEmptyString().GetTaggedValue();
}
std::u16string u16str;
uint32_t argc = argv->GetArgsNumber() - 1;
bool canBeCompress = true;
for (uint32_t i = 0, argsI = 1; i < static_cast<uint32_t>(length); ++i, ++argsI) {
// Let nextSeg be ToString(Get(raw, nextKey)).
JSHandle<JSTaggedValue> elementString =
JSObject::GetProperty(thread, JSHandle<JSTaggedValue>::Cast(rawObj), i).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *nextSeg = *JSTaggedValue::ToString(thread, elementString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
u16str += EcmaStringAccessor(nextSeg).ToU16String();
if (EcmaStringAccessor(nextSeg).IsUtf16()) {
canBeCompress = false;
}
if (i + 1 == static_cast<uint32_t>(length)) {
break;
}
if (argsI <= argc) {
EcmaString *nextSub = *JSTaggedValue::ToString(thread, GetCallArg(argv, argsI));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
u16str += EcmaStringAccessor(nextSub).ToU16String();
if (EcmaStringAccessor(nextSub).IsUtf16()) {
canBeCompress = false;
}
}
}
// return the result string
auto *uint16tData = reinterpret_cast<uint16_t *>(const_cast<char16_t *>(u16str.data()));
return canBeCompress ? factory->NewFromUtf16LiteralCompress(uint16tData, u16str.size()).GetTaggedValue() :
factory->NewFromUtf16LiteralNotCompress(uint16tData, u16str.size()).GetTaggedValue();
}
// 21.1.3.1
JSTaggedValue BuiltinsString::CharAt(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, CharAt);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisFlat(thread, EcmaStringAccessor::Flatten(thread->GetEcmaVM(), thisHandle));
int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisFlat).GetLength());
JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0);
int32_t pos = 0;
if (posTag->IsInt()) {
pos = posTag->GetInt();
} else if (posTag->IsUndefined()) {
pos = 0;
} else {
JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
pos = posVal.ToInt32();
}
if (pos < 0 || pos >= thisLen) {
return factory->GetEmptyString().GetTaggedValue();
}
uint16_t res = EcmaStringAccessor(thisFlat).Get<false>(pos);
return factory->NewFromUtf16Literal(&res, 1).GetTaggedValue();
}
// 21.1.3.2
JSTaggedValue BuiltinsString::CharCodeAt(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, CharCodeAt);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisFlat(thread, EcmaStringAccessor::Flatten(thread->GetEcmaVM(), thisHandle));
int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisFlat).GetLength());
JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0);
int32_t pos = 0;
if (posTag->IsInt()) {
pos = posTag->GetInt();
} else if (posTag->IsUndefined()) {
pos = 0;
} else {
JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
pos = posVal.ToInt32();
}
if (pos < 0 || pos >= thisLen) {
return GetTaggedDouble(base::NAN_VALUE);
}
uint16_t ret = EcmaStringAccessor(thisFlat).Get<false>(pos);
return GetTaggedInt(ret);
}
// 21.1.3.3
JSTaggedValue BuiltinsString::CodePointAt(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, CodePointAt);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisFlat(thread, EcmaStringAccessor::Flatten(thread->GetEcmaVM(), thisHandle));
JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 0);
JSTaggedNumber posVal = JSTaggedValue::ToNumber(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t pos = base::NumberHelper::DoubleInRangeInt32(posVal.GetNumber());
int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisFlat).GetLength());
if (pos < 0 || pos >= thisLen) {
return JSTaggedValue::Undefined();
}
uint16_t first = EcmaStringAccessor(thisFlat).Get<false>(pos);
if (first < base::utf_helper::DECODE_LEAD_LOW || first > base::utf_helper::DECODE_LEAD_HIGH || pos + 1 == thisLen) {
return GetTaggedInt(first);
}
uint16_t second = EcmaStringAccessor(thisFlat).Get<false>(pos + 1);
if (second < base::utf_helper::DECODE_TRAIL_LOW || second > base::utf_helper::DECODE_TRAIL_HIGH) {
return GetTaggedInt(first);
}
uint32_t res = base::utf_helper::UTF16Decode(first, second);
return GetTaggedInt(res);
}
// 21.1.3.4
JSTaggedValue BuiltinsString::Concat(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Concat);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
auto ecmaVm = thread->GetEcmaVM();
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t argLength = argv->GetArgsNumber();
if (argLength == 0) {
return thisHandle.GetTaggedValue();
}
for (uint32_t i = 0; i < argLength; i++) {
JSHandle<JSTaggedValue> nextTag = BuiltinsString::GetCallArg(argv, i);
JSHandle<EcmaString> nextHandle = JSTaggedValue::ToString(thread, nextTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *tempStr = EcmaStringAccessor::Concat(ecmaVm, thisHandle, nextHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
thisHandle = JSHandle<EcmaString>(thread, tempStr);
}
return thisHandle.GetTaggedValue();
}
// 21.1.3.5 String.prototype.constructor
// 21.1.3.6
JSTaggedValue BuiltinsString::EndsWith(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, EndsWith);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
bool isRegexp = JSObject::IsRegExp(thread, searchTag);
if (isRegexp) {
THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception());
}
JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength();
uint32_t searchLen = EcmaStringAccessor(searchHandle).GetLength();
uint32_t pos = 0;
JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
if (posTag->IsUndefined()) {
pos = thisLen;
} else {
JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
pos = static_cast<uint32_t>(posVal.ToInt32());
}
uint32_t end = std::min(std::max(pos, 0U), thisLen);
int32_t start = static_cast<int32_t>(end - searchLen);
if (start < 0) {
return BuiltinsString::GetTaggedBoolean(false);
}
int32_t idx = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, start);
if (idx == start) {
return BuiltinsString::GetTaggedBoolean(true);
}
return BuiltinsString::GetTaggedBoolean(false);
}
// 21.1.3.7
JSTaggedValue BuiltinsString::Includes(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Includes);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
bool isRegexp = JSObject::IsRegExp(thread, searchTag);
if (isRegexp) {
THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception());
}
JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength();
int32_t pos = 0;
JSHandle<JSTaggedValue> posTag = BuiltinsBase::GetCallArg(argv, 1);
if (argv->GetArgsNumber() == 1) {
pos = 0;
} else {
JSTaggedNumber posVal = JSTaggedValue::ToNumber(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
pos = base::NumberHelper::DoubleInRangeInt32(posVal.GetNumber());
}
int32_t start = std::min(std::max(pos, 0), static_cast<int32_t>(thisLen));
int32_t idx = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, start);
if (idx < 0 || idx > static_cast<int32_t>(thisLen)) {
return BuiltinsString::GetTaggedBoolean(false);
}
return BuiltinsString::GetTaggedBoolean(true);
}
// 21.1.3.8
JSTaggedValue BuiltinsString::IndexOf(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, IndexOf);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength();
JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
int32_t pos = 0;
if (posTag->IsInt()) {
pos = posTag->GetInt();
} else if (posTag->IsUndefined()) {
pos = 0;
} else {
JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
pos = posVal.ToInt32();
}
pos = std::min(std::max(pos, 0), static_cast<int32_t>(thisLen));
int32_t res = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, pos);
if (res >= 0 && res < static_cast<int32_t>(thisLen)) {
return GetTaggedInt(res);
}
return GetTaggedInt(-1);
}
// 21.1.3.9
JSTaggedValue BuiltinsString::LastIndexOf(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, LastIndexOf);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength());
JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t pos = 0;
if (argv->GetArgsNumber() == 1) {
pos = thisLen;
} else {
JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (std::isnan(JSTaggedValue::ToNumber(thread, posTag).GetNumber())) {
pos = thisLen;
} else {
pos = posVal.ToInt32();
}
}
pos = std::min(std::max(pos, 0), thisLen);
int32_t res = EcmaStringAccessor::LastIndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, pos);
if (res >= 0 && res < thisLen) {
return GetTaggedInt(res);
}
res = -1;
return GetTaggedInt(static_cast<int32_t>(res));
}
// 21.1.3.10
JSTaggedValue BuiltinsString::LocaleCompare(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, LocaleCompare);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thatTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
[[maybe_unused]] JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
[[maybe_unused]] JSHandle<EcmaString> thatHandle = JSTaggedValue::ToString(thread, thatTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> locales = GetCallArg(argv, 1);
JSHandle<JSTaggedValue> options = GetCallArg(argv, 2); // 2: the second argument
[[maybe_unused]] bool cacheable = (locales->IsUndefined() || locales->IsString()) && options->IsUndefined();
#ifdef ARK_SUPPORT_INTL
if (cacheable) {
auto collator = JSCollator::GetCachedIcuCollator(thread, locales);
if (collator != nullptr) {
JSTaggedValue result = JSCollator::CompareStrings(collator, thisHandle, thatHandle);
return result;
}
}
EcmaVM *ecmaVm = thread->GetEcmaVM();
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<JSTaggedValue> ctor = ecmaVm->GetGlobalEnv()->GetCollatorFunction();
JSHandle<JSCollator> collator =
JSHandle<JSCollator>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor)));
JSHandle<JSCollator> initCollator =
JSCollator::InitializeCollator(thread, collator, locales, options, cacheable);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
icu::Collator *icuCollator = nullptr;
if (cacheable) {
icuCollator = JSCollator::GetCachedIcuCollator(thread, locales);
ASSERT(icuCollator != nullptr);
} else {
icuCollator = initCollator->GetIcuCollator();
}
JSTaggedValue result = JSCollator::CompareStrings(icuCollator, thisHandle, thatHandle);
return result;
#else
#ifdef ARK_NOT_SUPPORT_INTL_GLOBAL
ARK_SUPPORT_INTL_RETURN_JSVALUE(thread, "LocaleCompare");
#else
intl::GlobalIntlHelper gh(thread, intl::GlobalFormatterType::Collator);
auto collator = gh.GetGlobalObject<intl::GlobalCollator>(thread,
locales, options, intl::GlobalFormatterType::Collator, cacheable);
if (collator == nullptr) {
LOG_ECMA(ERROR) << "BuiltinsString::LocaleCompare:collator is nullptr";
}
ASSERT(collator != nullptr);
auto result = collator->Compare(EcmaStringAccessor(thisHandle).ToStdString(),
EcmaStringAccessor(thatHandle).ToStdString());
return JSTaggedValue(result);
#endif
#endif
}
JSTaggedValue BuiltinsString::LocaleCompareGC(JSThread *thread, JSHandle<JSTaggedValue> locales,
JSHandle<EcmaString> thisHandle, JSHandle<EcmaString> thatHandle,
JSHandle<JSTaggedValue> options, bool cacheable)
{
EcmaVM *ecmaVm = thread->GetEcmaVM();
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<JSTaggedValue> ctor = ecmaVm->GetGlobalEnv()->GetCollatorFunction();
JSHandle<JSCollator> collator =
JSHandle<JSCollator>::Cast(factory->NewJSObjectByConstructor(JSHandle<JSFunction>(ctor)));
JSHandle<JSCollator> initCollator =
JSCollator::InitializeCollator(thread, collator, locales, options, cacheable);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
icu::Collator *icuCollator = nullptr;
if (cacheable) {
icuCollator = JSCollator::GetCachedIcuCollator(thread, locales);
ASSERT(icuCollator != nullptr);
} else {
icuCollator = initCollator->GetIcuCollator();
}
JSTaggedValue result = JSCollator::CompareStrings(icuCollator, thisHandle, thatHandle);
return result;
}
// 21.1.3.11
JSTaggedValue BuiltinsString::Match(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Match);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> matchTag = thread->GetEcmaVM()->GetGlobalEnv()->GetMatchSymbol();
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
if (regexp->IsJSRegExp()) {
JSHandle<RegExpExecResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache());
JSHandle<JSRegExp> re(regexp);
JSHandle<JSTaggedValue> pattern(thread, re->GetOriginalSource());
JSHandle<JSTaggedValue> flags(thread, re->GetOriginalFlags());
JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, pattern, flags, thisTag,
RegExpExecResultCache::MATCH_TYPE, regexp,
JSTaggedValue(0));
if (!cacheResult.IsUndefined()) {
return cacheResult;
}
}
if (!regexp->IsUndefined() && !regexp->IsNull()) {
if (regexp->IsECMAObject()) {
JSHandle<JSTaggedValue> matcher = JSObject::GetMethod(thread, regexp, matchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!matcher->IsUndefined()) {
ASSERT(matcher->IsJSFunction());
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, matcher, regexp, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisTag.GetTaggedValue());
return JSFunction::Call(info);
}
}
}
JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> undifinedHandle = globalConst->GetHandledUndefined();
JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, undifinedHandle));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, undefined, rx, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisVal.GetTaggedValue());
return JSFunction::Invoke(info, matchTag);
}
JSTaggedValue BuiltinsString::MatchAll(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, MatchAll);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
// 1. Let O be ? RequireObjectCoercible(this value).
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> matchAllTag = thread->GetEcmaVM()->GetGlobalEnv()->GetMatchAllSymbol();
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
auto ecmaVm = thread->GetEcmaVM();
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<JSTaggedValue> gvalue(factory->NewFromASCII("g"));
// 2. If regexp is neither undefined nor null, then
if (!regexp->IsUndefined() && !regexp->IsNull()) {
// a. Let isRegExp be ? IsRegExp(searchValue).
bool isJSRegExp = JSObject::IsRegExp(thread, regexp);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// b. If isRegExp is true, then
if (isJSRegExp) {
// i. Let flags be ? Get(searchValue, "flags").
JSHandle<JSTaggedValue> flagsString(globalConst->GetHandledFlagsString());
JSHandle<JSTaggedValue> flags = JSObject::GetProperty(thread, regexp, flagsString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// ii. Perform ? RequireObjectCoercible(flags).
JSTaggedValue::RequireObjectCoercible(thread, flags);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// iii. If ? ToString(flags) does not contain "g", throw a TypeError exception.
JSHandle<EcmaString> flagString = JSTaggedValue::ToString(thread, flags);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm,
flagString, JSHandle<EcmaString>(gvalue));
if (pos == -1) {
THROW_TYPE_ERROR_AND_RETURN(thread,
"matchAll called with a non-global RegExp argument",
JSTaggedValue::Exception());
}
}
if (regexp->IsECMAObject()) {
// c. c. Let matcher be ? GetMethod(regexp, @@matchAll).
// d. d. If matcher is not undefined, then
JSHandle<JSTaggedValue> matcher = JSObject::GetMethod(thread, regexp, matchAllTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!matcher->IsUndefined()) {
ASSERT(matcher->IsJSFunction());
// i. i. Return ? Call(matcher, regexp, « O »).
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, matcher, regexp, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisTag.GetTaggedValue());
return JSFunction::Call(info);
}
}
}
// 3. Let S be ? ToString(O).
JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// 4. Let rx be ? RegExpCreate(regexp, "g").
JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, gvalue));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, undefined, rx, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisVal.GetTaggedValue());
return JSFunction::Invoke(info, matchAllTag);
}
// 21.1.3.12
JSTaggedValue BuiltinsString::Normalize(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Normalize);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
auto vm = thread->GetEcmaVM();
ObjectFactory *factory = vm->GetFactory();
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
JSHandle<EcmaString> formValue;
if (argv->GetArgsNumber() == 0) {
formValue = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledNfcString());
} else {
JSHandle<JSTaggedValue> formTag = BuiltinsString::GetCallArg(argv, 0);
if (formTag->IsUndefined()) {
formValue = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledNfcString());
} else {
formValue = JSTaggedValue::ToString(thread, formTag);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception());
}
}
JSHandle<EcmaString> nfc = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledNfcString());
JSHandle<EcmaString> nfd = factory->NewFromASCII("NFD");
JSHandle<EcmaString> nfkc = factory->NewFromASCII("NFKC");
JSHandle<EcmaString> nfkd = factory->NewFromASCII("NFKD");
UNormalizationMode uForm;
if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfc)) {
uForm = UNORM_NFC;
} else if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfd)) {
uForm = UNORM_NFD;
} else if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfkc)) {
uForm = UNORM_NFKC;
} else if (EcmaStringAccessor::StringsAreEqual(vm, formValue, nfkd)) {
uForm = UNORM_NFKD;
} else {
THROW_RANGE_ERROR_AND_RETURN(thread, "compare not equal", JSTaggedValue::Exception());
}
std::u16string u16strThis = EcmaStringAccessor(thisHandle).ToU16String();
const char16_t *constChar16tData = u16strThis.data();
icu::UnicodeString src(constChar16tData, u16strThis.size());
icu::UnicodeString res;
UErrorCode errorCode = U_ZERO_ERROR;
int32_t option = 0;
icu::Normalizer::normalize(src, uForm, option, res, errorCode);
JSHandle<EcmaString> str = intl::LocaleHelper::UStringToString(thread, res);
return JSTaggedValue(*str);
}
JSTaggedValue BuiltinsString::PadStart(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, PadStart);
return BuiltinsString::Pad(argv, true);
}
JSTaggedValue BuiltinsString::PadEnd(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, PadEnd);
return BuiltinsString::Pad(argv, false);
}
// 21.1.3.13
JSTaggedValue BuiltinsString::Repeat(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Repeat);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength();
JSHandle<JSTaggedValue> countTag = BuiltinsString::GetCallArg(argv, 0);
int32_t count = 0;
if (countTag->IsInt()) {
count = countTag->GetInt();
} else {
JSTaggedNumber num = JSTaggedValue::ToInteger(thread, countTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
double d = num.GetNumber();
if (d == base::POSITIVE_INFINITY) {
THROW_RANGE_ERROR_AND_RETURN(thread, "is infinity", JSTaggedValue::Exception());
}
count = base::NumberHelper::DoubleInRangeInt32(d);
}
if (count < 0) {
THROW_RANGE_ERROR_AND_RETURN(thread, "less than 0", JSTaggedValue::Exception());
}
if (count == 0) {
auto emptyStr = thread->GetEcmaVM()->GetFactory()->GetEmptyString();
return emptyStr.GetTaggedValue();
}
if (thisLen == 0) {
return thisHandle.GetTaggedValue();
}
bool isUtf8 = EcmaStringAccessor(thisHandle).IsUtf8();
EcmaString *result = EcmaStringAccessor::CreateLineString(thread->GetEcmaVM(), thisLen * count, isUtf8);
for (uint32_t index = 0; index < static_cast<uint32_t>(count); ++index) {
EcmaStringAccessor::ReadData(result, *thisHandle, index * thisLen, (count - index) * thisLen, thisLen);
}
return JSTaggedValue(result);
}
// 21.1.3.14
JSTaggedValue BuiltinsString::Replace(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Replace);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, BuiltinsString::GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
auto ecmaVm = thread->GetEcmaVM();
JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> replaceTag = BuiltinsString::GetCallArg(argv, 1);
ObjectFactory *factory = ecmaVm->GetFactory();
if (searchTag->IsJSRegExp() && replaceTag->IsString()) {
JSHandle<RegExpExecResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetRegExpCache());
JSHandle<JSRegExp> re(searchTag);
JSHandle<JSTaggedValue> pattern(thread, re->GetOriginalSource());
JSHandle<JSTaggedValue> flags(thread, re->GetOriginalFlags());
JSTaggedValue cacheResult = cacheTable->FindCachedResult(thread, pattern, flags, thisTag,
RegExpExecResultCache::REPLACE_TYPE, searchTag,
replaceTag.GetTaggedValue());
if (!cacheResult.IsUndefined()) {
return cacheResult;
}
}
if (searchTag->IsJSRegExp() && PropertyDetector::IsRegExpReplaceDetectorValid(env)) {
JSTaggedValue proto = JSObject::GetPrototype(JSHandle<JSObject>(searchTag));
if (proto == env->GetTaggedRegExpPrototype()) {
return BuiltinsRegExp::ReplaceInternal(thread, searchTag, thisTag, replaceTag);
}
}
// If searchValue is neither undefined nor null, then
if (searchTag->IsECMAObject()) {
JSHandle<JSTaggedValue> replaceKey = env->GetReplaceSymbol();
// Let replacer be GetMethod(searchValue, @@replace).
JSHandle<JSTaggedValue> replaceMethod = JSObject::GetMethod(thread, searchTag, replaceKey);
// ReturnIfAbrupt(replacer).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// If replacer is not undefined, then
if (!replaceMethod->IsUndefined()) {
// Return Call(replacer, searchValue, «O, replaceValue»).
const uint32_t argsLength = 2;
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, replaceMethod, searchTag, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisTag.GetTaggedValue(), replaceTag.GetTaggedValue());
return JSFunction::Call(info);
}
}
// Let string be ToString(O).
JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag);
// ReturnIfAbrupt(string).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let searchString be ToString(searchValue).
JSHandle<EcmaString> searchString = JSTaggedValue::ToString(thread, searchTag);
// ReturnIfAbrupt(searchString).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let functionalReplace be IsCallable(replaceValue).
if (!replaceTag->IsCallable()) {
// If functionalReplace is false, then
// Let replaceValue be ToString(replaceValue).
// ReturnIfAbrupt(replaceValue)
replaceTag = JSHandle<JSTaggedValue>(JSTaggedValue::ToString(thread, replaceTag));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
// Search string for the first occurrence of searchString and let pos be the index within string of the first code
// unit of the matched substring and let matched be searchString. If no occurrences of searchString were found,
// return string.
int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, searchString);
if (pos == -1) {
return thisString.GetTaggedValue();
}
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
JSMutableHandle<JSTaggedValue> replHandle(thread, factory->GetEmptyString().GetTaggedValue());
// If functionalReplace is true, then
if (replaceTag->IsCallable()) {
// Let replValue be Call(replaceValue, undefined,«matched, pos, and string»).
const uint32_t argsLength = 3; // 3: «matched, pos, and string»
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, replaceTag, undefined, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(searchString.GetTaggedValue(), JSTaggedValue(pos), thisString.GetTaggedValue());
JSTaggedValue replStrDeocodeValue = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
replHandle.Update(replStrDeocodeValue);
} else {
// Let captures be an empty List.
JSHandle<TaggedArray> capturesList = factory->EmptyArray();
ASSERT_PRINT(replaceTag->IsString(), "replace must be string");
JSHandle<EcmaString> replacement(thread, replaceTag->GetTaggedObject());
// Let replStr be GetSubstitution(matched, string, pos, captures, replaceValue)
replHandle.Update(GetSubstitution(thread, searchString, thisString, pos, capturesList, undefined, replacement));
}
JSHandle<EcmaString> realReplaceStr = JSTaggedValue::ToString(thread, replHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let tailPos be pos + the number of code units in matched.
int32_t tailPos = pos + static_cast<int32_t>(EcmaStringAccessor(searchString).GetLength());
// Let newString be the String formed by concatenating the first pos code units of string,
// replStr, and the trailing
// substring of string starting at index tailPos. If pos is 0,
// the first element of the concatenation will be the
// empty String.
// Return newString.
JSHandle<EcmaString> prefixString(thread, EcmaStringAccessor::FastSubString(ecmaVm, thisString, 0, pos));
auto thisLen = EcmaStringAccessor(thisString).GetLength();
JSHandle<EcmaString> suffixString(thread,
EcmaStringAccessor::FastSubString(ecmaVm, thisString, tailPos, thisLen - tailPos));
EcmaString *tempStr = EcmaStringAccessor::Concat(ecmaVm, prefixString, realReplaceStr);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> tempString(thread, tempStr);
EcmaString *resultStr = EcmaStringAccessor::Concat(ecmaVm, tempString, suffixString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return JSTaggedValue(resultStr);
}
JSTaggedValue BuiltinsString::ReplaceAll(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, String, ReplaceAll);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, BuiltinsString::GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
auto ecmaVm = thread->GetEcmaVM();
JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> replaceTag = BuiltinsString::GetCallArg(argv, 1);
ObjectFactory *factory = ecmaVm->GetFactory();
if (!searchTag->IsUndefined() && !searchTag->IsNull()) {
// a. Let isRegExp be ? IsRegExp(searchValue).
bool isJSRegExp = JSObject::IsRegExp(thread, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// b. If isRegExp is true, then
if (isJSRegExp) {
// i. Let flags be ? Get(searchValue, "flags").
JSHandle<JSTaggedValue> flagsString(globalConst->GetHandledFlagsString());
JSHandle<JSTaggedValue> flags = JSObject::GetProperty(thread, searchTag, flagsString).GetValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// ii. Perform ? RequireObjectCoercible(flags).
JSTaggedValue::RequireObjectCoercible(thread, flags);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// iii. If ? ToString(flags) does not contain "g", throw a TypeError exception.
JSHandle<EcmaString> flagString = JSTaggedValue::ToString(thread, flags);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> gString(globalConst->GetHandledGString());
int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, flagString, gString);
if (pos == -1) {
THROW_TYPE_ERROR_AND_RETURN(thread,
"string.prototype.replaceAll called with a non-global RegExp argument",
JSTaggedValue::Exception());
}
}
// c. Let replacer be ? GetMethod(searchValue, @@replace).
JSHandle<JSTaggedValue> replaceKey = env->GetReplaceSymbol();
JSHandle<JSTaggedValue> replaceMethod = JSObject::GetMethod(thread, searchTag, replaceKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// d. If replacer is not undefined, then
if (!replaceMethod->IsUndefined()) {
// i. Return ? Call(replacer, searchValue, «O, replaceValue»).
const size_t argsLength = 2;
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, replaceMethod, searchTag, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisTag.GetTaggedValue(), replaceTag.GetTaggedValue());
return JSFunction::Call(info);
}
}
// 3. Let string be ? ToString(O).
JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// 4. Let searchString be ? ToString(searchValue).
JSHandle<EcmaString> searchString = JSTaggedValue::ToString(thread, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// 5. Let functionalReplace be IsCallable(replaceValue).
// 6. If functionalReplace is false, then
if (!replaceTag->IsCallable()) {
// a. Set replaceValue to ? ToString(replaceValue).
replaceTag = JSHandle<JSTaggedValue>(JSTaggedValue::ToString(thread, replaceTag));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
// 7. Let searchLength be the length of searchString.
// 8. Let advanceBy be max(1, searchLength).
int32_t searchLength = static_cast<int32_t>(EcmaStringAccessor(searchString).GetLength());
int32_t advanceBy = std::max(1, searchLength);
// 9. Let matchPositions be a new empty List.
std::u16string stringBuilder;
std::u16string stringPrefixString;
std::u16string stringRealReplaceStr;
std::u16string stringSuffixString;
// 10. Let position be ! StringIndexOf(string, searchString, 0).
int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, searchString);
int32_t endOfLastMatch = 0;
bool canBeCompress = true;
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
JSMutableHandle<JSTaggedValue> replHandle(thread, factory->GetEmptyString().GetTaggedValue());
while (pos != -1) {
// If functionalReplace is true, then
if (replaceTag->IsCallable()) {
// Let replValue be Call(replaceValue, undefined,«matched, pos, and string»).
const uint32_t argsLength = 3; // 3: «matched, pos, and string»
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, replaceTag, undefined, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(searchString.GetTaggedValue(), JSTaggedValue(pos), thisString.GetTaggedValue());
JSTaggedValue replStrDeocodeValue = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
replHandle.Update(replStrDeocodeValue);
} else {
// Let captures be an empty List.
JSHandle<TaggedArray> capturesList = factory->NewTaggedArray(0);
ASSERT_PRINT(replaceTag->IsString(), "replace must be string");
JSHandle<EcmaString> replacement(thread, replaceTag->GetTaggedObject());
// Let replStr be GetSubstitution(matched, string, pos, captures, replaceValue)
replHandle.Update(GetSubstitution(thread, searchString, thisString, pos,
capturesList, undefined, replacement));
}
JSHandle<EcmaString> realReplaceStr = JSTaggedValue::ToString(thread, replHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let tailPos be pos + the number of code units in matched.
// Let newString be the String formed by concatenating the first pos code units of string,
// replStr, and the trailing substring of string starting at index tailPos.
// If pos is 0, the first element of the concatenation will be the
// empty String.
// Return newString.
JSHandle<EcmaString> prefixString(thread,
EcmaStringAccessor::FastSubString(ecmaVm, thisString, endOfLastMatch,
pos - endOfLastMatch));
stringPrefixString = EcmaStringAccessor(prefixString).ToU16String();
if (EcmaStringAccessor(prefixString).IsUtf16()) {
canBeCompress = false;
}
stringRealReplaceStr = EcmaStringAccessor(realReplaceStr).ToU16String();
if (EcmaStringAccessor(realReplaceStr).IsUtf16()) {
canBeCompress = false;
}
stringBuilder = stringBuilder + stringPrefixString + stringRealReplaceStr;
endOfLastMatch = pos + searchLength;
pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, searchString, pos + advanceBy);
}
if (endOfLastMatch < static_cast<int32_t>(EcmaStringAccessor(thisString).GetLength())) {
auto thisLen = EcmaStringAccessor(thisString).GetLength();
JSHandle<EcmaString> suffixString(thread,
EcmaStringAccessor::FastSubString(ecmaVm, thisString, endOfLastMatch, thisLen - endOfLastMatch));
stringSuffixString = EcmaStringAccessor(suffixString).ToU16String();
if (EcmaStringAccessor(suffixString).IsUtf16()) {
canBeCompress = false;
}
stringBuilder = stringBuilder + stringSuffixString;
}
auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str());
auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
return canBeCompress ?
factory->NewFromUtf16LiteralCompress(uint16tData, stringBuilder.length()).GetTaggedValue() :
factory->NewFromUtf16LiteralNotCompress(uint16tData, stringBuilder.length()).GetTaggedValue();
}
JSTaggedValue BuiltinsString::GetSubstitution(JSThread *thread, const JSHandle<EcmaString> &matched,
const JSHandle<EcmaString> &srcString, int position,
const JSHandle<TaggedArray> &captureList,
const JSHandle<JSTaggedValue> &namedCaptures,
const JSHandle<EcmaString> &replacement)
{
BUILTINS_API_TRACE(thread, String, GetSubstitution);
auto ecmaVm = thread->GetEcmaVM();
ObjectFactory *factory = ecmaVm->GetFactory();
JSHandle<EcmaString> dollarString = JSHandle<EcmaString>::Cast(thread->GlobalConstants()->GetHandledDollarString());
JSHandle<EcmaString> replacementFlat(thread, EcmaStringAccessor::Flatten(ecmaVm, replacement));
int32_t replaceLength = static_cast<int32_t>(EcmaStringAccessor(replacementFlat).GetLength());
int32_t tailPos = position + static_cast<int32_t>(EcmaStringAccessor(matched).GetLength());
int32_t nextDollarIndex = EcmaStringAccessor::IndexOf(ecmaVm, replacementFlat, dollarString);
if (nextDollarIndex < 0) {
return replacementFlat.GetTaggedValue();
}
std::u16string stringBuilder;
bool canBeCompress = true;
if (nextDollarIndex > 0) {
stringBuilder = EcmaStringAccessor(replacementFlat).ToU16String(nextDollarIndex);
if (EcmaStringAccessor(replacementFlat).IsUtf16()) {
canBeCompress = false;
}
}
while (true) {
int peekIndex = nextDollarIndex + 1;
if (peekIndex >= replaceLength) {
stringBuilder += '$';
auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str());
auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
return canBeCompress ?
factory->NewFromUtf16LiteralCompress(uint16tData, stringBuilder.length()).GetTaggedValue() :
factory->NewFromUtf16LiteralNotCompress(uint16tData, stringBuilder.length()).GetTaggedValue();
}
int continueFromIndex = -1;
uint16_t peek = EcmaStringAccessor(replacementFlat).Get(peekIndex);
switch (peek) {
case '$': // $$
stringBuilder += '$';
continueFromIndex = peekIndex + 1;
break;
case '&': // $& - match
stringBuilder += EcmaStringAccessor(matched).ToU16String();
if (EcmaStringAccessor(matched).IsUtf16()) {
canBeCompress = false;
}
continueFromIndex = peekIndex + 1;
break;
case '`': // $` - prefix
if (position > 0) {
EcmaString *prefix = EcmaStringAccessor::FastSubString(ecmaVm, srcString, 0, position);
stringBuilder += EcmaStringAccessor(prefix).ToU16String();
if (EcmaStringAccessor(prefix).IsUtf16()) {
canBeCompress = false;
}
}
continueFromIndex = peekIndex + 1;
break;
case '\'': {
// $' - suffix
int32_t srcLength = static_cast<int32_t>(EcmaStringAccessor(srcString).GetLength());
if (tailPos < srcLength) {
EcmaString *sufffix = EcmaStringAccessor::FastSubString(
ecmaVm, srcString, tailPos, srcLength - tailPos);
stringBuilder += EcmaStringAccessor(sufffix).ToU16String();
if (EcmaStringAccessor(sufffix).IsUtf16()) {
canBeCompress = false;
}
}
continueFromIndex = peekIndex + 1;
break;
}
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
uint32_t capturesLength = captureList->GetLength();
// Valid indices are $1 .. $9, $01 .. $09 and $10 .. $99
uint32_t scaledIndex = peek - '0';
int32_t advance = 1;
if (peekIndex + 1 < replaceLength) {
uint16_t nextPeek = EcmaStringAccessor(replacementFlat).Get(peekIndex + 1);
if (nextPeek >= '0' && nextPeek <= '9') {
constexpr uint32_t TEN_BASE = 10;
uint32_t newScaledIndex = scaledIndex * TEN_BASE + (nextPeek - '0');
if (newScaledIndex <= capturesLength) {
scaledIndex = newScaledIndex;
advance = 2; // 2: 2 means from index needs to add two.
}
}
}
if (scaledIndex == 0 || scaledIndex > capturesLength) {
stringBuilder += '$';
continueFromIndex = peekIndex;
break;
}
JSTaggedValue capturesVal(captureList->Get(scaledIndex - 1));
if (!capturesVal.IsUndefined()) {
EcmaString *captureString = EcmaString::Cast(capturesVal.GetTaggedObject());
stringBuilder += EcmaStringAccessor(captureString).ToU16String();
if (EcmaStringAccessor(captureString).IsUtf16()) {
canBeCompress = false;
}
}
continueFromIndex = peekIndex + advance;
break;
}
case '<': {
if (namedCaptures->IsUndefined()) {
stringBuilder += '$';
continueFromIndex = peekIndex;
break;
}
JSHandle<EcmaString> greaterSymString = factory->NewFromASCII(">");
int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, replacementFlat, greaterSymString, peekIndex);
if (pos == -1) {
stringBuilder += '$';
continueFromIndex = peekIndex;
break;
}
JSHandle<EcmaString> groupName(thread,
EcmaStringAccessor::FastSubString(ecmaVm, replacementFlat,
peekIndex + 1, pos - peekIndex - 1));
JSHandle<JSTaggedValue> names(groupName);
JSHandle<JSTaggedValue> capture = JSObject::GetProperty(thread, namedCaptures, names).GetValue();
if (capture->IsUndefined()) {
continueFromIndex = pos + 1;
break;
}
JSHandle<EcmaString> captureName = JSTaggedValue::ToString(thread, capture);
stringBuilder += EcmaStringAccessor(captureName).ToU16String();
if (EcmaStringAccessor(captureName).IsUtf16()) {
canBeCompress = false;
}
continueFromIndex = pos + 1;
break;
}
default:
stringBuilder += '$';
continueFromIndex = peekIndex;
break;
}
// Go the the next $ in the replacement.
nextDollarIndex = EcmaStringAccessor::IndexOf(ecmaVm, replacementFlat, dollarString, continueFromIndex);
if (nextDollarIndex < 0) {
if (continueFromIndex < replaceLength) {
EcmaString *nextAppend = EcmaStringAccessor::FastSubString(ecmaVm, replacementFlat, continueFromIndex,
replaceLength - continueFromIndex);
stringBuilder += EcmaStringAccessor(nextAppend).ToU16String();
if (EcmaStringAccessor(nextAppend).IsUtf16()) {
canBeCompress = false;
}
}
auto *char16tData = const_cast<char16_t *>(stringBuilder.c_str());
auto *uint16tData = reinterpret_cast<uint16_t *>(char16tData);
return canBeCompress ?
factory->NewFromUtf16LiteralCompress(uint16tData, stringBuilder.length()).GetTaggedValue() :
factory->NewFromUtf16LiteralNotCompress(uint16tData, stringBuilder.length()).GetTaggedValue();
}
// Append substring between the previous and the next $ character.
if (nextDollarIndex > continueFromIndex) {
EcmaString *nextAppend = EcmaStringAccessor::FastSubString(
ecmaVm, replacementFlat, continueFromIndex, nextDollarIndex - continueFromIndex);
stringBuilder += EcmaStringAccessor(nextAppend).ToU16String();
if (EcmaStringAccessor(nextAppend).IsUtf16()) {
canBeCompress = false;
}
}
}
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
// 21.1.3.15
JSTaggedValue BuiltinsString::Search(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Search);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> regexp = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> searchTag = thread->GetEcmaVM()->GetGlobalEnv()->GetSearchSymbol();
JSHandle<JSTaggedValue> undefined = globalConst->GetHandledUndefined();
if (!regexp->IsUndefined() && !regexp->IsNull()) {
if (regexp->IsECMAObject()) {
JSHandle<JSTaggedValue> searcher = JSObject::GetMethod(thread, regexp, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!searcher->IsUndefined()) {
ASSERT(searcher->IsJSFunction());
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, searcher, regexp, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisTag.GetTaggedValue());
return JSFunction::Call(info);
}
}
}
JSHandle<EcmaString> thisVal = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> rx(thread, BuiltinsRegExp::RegExpCreate(thread, regexp, undefined));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, undefined, rx, undefined, 1);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisVal.GetTaggedValue());
return JSFunction::Invoke(info, searchTag);
}
// 21.1.3.16
JSTaggedValue BuiltinsString::Slice(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Slice);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength());
JSHandle<JSTaggedValue> startTag = BuiltinsString::GetCallArg(argv, 0);
JSTaggedNumber startVal = JSTaggedValue::ToInteger(thread, startTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t start = ConvertDoubleToInt(startVal.GetNumber());
int32_t end = 0;
JSHandle<JSTaggedValue> endTag = BuiltinsString::GetCallArg(argv, 1);
if (endTag->IsUndefined()) {
end = thisLen;
} else {
JSTaggedNumber endVal = JSTaggedValue::ToInteger(thread, endTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
end = ConvertDoubleToInt(endVal.GetNumber());
}
int32_t from = 0;
int32_t to = 0;
if (start < 0) {
from = std::max(start + thisLen, 0);
} else {
from = std::min(start, thisLen);
}
if (end < 0) {
to = std::max(end + thisLen, 0);
} else {
to = std::min(end, thisLen);
}
int32_t len = std::max(to - from, 0);
return JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), thisHandle, from, len));
}
// 21.1.3.17
JSTaggedValue BuiltinsString::Split(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Split);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
auto ecmaVm = thread->GetEcmaVM();
JSHandle<GlobalEnv> env = ecmaVm->GetGlobalEnv();
// Let O be RequireObjectCoercible(this value).
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> seperatorTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> limitTag = BuiltinsString::GetCallArg(argv, 1);
if (thisTag->IsString() && seperatorTag->IsString()) {
JSHandle<EcmaString> thisString(thisTag);
JSHandle<EcmaString> seperatorString(seperatorTag);
auto thisLength = EcmaStringAccessor(thisString).GetLength();
auto seperatorLength = EcmaStringAccessor(seperatorString).GetLength();
if (limitTag->IsUndefined() && thisLength != 0 && seperatorLength != 0) {
return CreateArrayThisStringAndSeperatorStringAreNotEmpty(
thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength);
}
uint32_t lim = UINT32_MAX - 1;
if (!limitTag->IsUndefined()) {
lim = JSTaggedValue::ToInteger(thread, limitTag).ToUint32();
}
// ReturnIfAbrupt(lim).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (lim == 0) {
JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return resultArray.GetTaggedValue();
}
return CreateArrayBySplitString(thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength, lim);
}
// If separator is neither undefined nor null, then
if (seperatorTag->IsECMAObject()) {
JSHandle<JSTaggedValue> splitKey = env->GetSplitSymbol();
// Let splitter be GetMethod(separator, @@split).
JSHandle<JSTaggedValue> splitter = JSObject::GetMethod(thread, seperatorTag, splitKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (!splitter->IsUndefined()) {
// Return Call(splitter, separator, «O, limit»).
const uint32_t argsLength = 2;
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, splitter, seperatorTag, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(thisTag.GetTaggedValue(), limitTag.GetTaggedValue());
return JSFunction::Call(info);
}
}
// Let S be ToString(O).
JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// If limit is undefined, let lim = 2^531; else let lim = ToLength(limit).
uint32_t lim = UINT32_MAX - 1;
if (!limitTag->IsUndefined()) {
lim = JSTaggedValue::ToInteger(thread, limitTag).ToUint32();
}
// ReturnIfAbrupt(lim).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let s be the number of elements in S.
auto thisLength = EcmaStringAccessor(thisString).GetLength();
JSHandle<EcmaString> seperatorString = JSTaggedValue::ToString(thread, seperatorTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// If lim = 0, return A.
if (lim == 0) {
JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return resultArray.GetTaggedValue();
}
auto seperatorLength = EcmaStringAccessor(seperatorString).GetLength();
// If S is undefined or (this.length = 0 and S.length != 0), return array of size is 1 containing this string
if (seperatorTag->IsUndefined()) {
JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(1)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Perform CreateDataProperty(A, "0", S), CreateDataProperty's fast path
JSObject::CreateDataProperty(thread, resultArray, 0, JSHandle<JSTaggedValue>(thisString));
ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty(A, \"0\", S) can't throw exception");
return resultArray.GetTaggedValue();
}
return CreateArrayBySplitString(thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength, lim);
}
JSTaggedValue BuiltinsString::CreateArrayFromString(JSThread *thread, EcmaVM *ecmaVm,
const JSHandle<EcmaString> &thisString, uint32_t thisLength, uint32_t lim)
{
uint32_t actualLength = std::min(thisLength, lim);
JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(actualLength)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
for (uint32_t i = 0; i < actualLength; ++i) {
EcmaString *elementString = EcmaStringAccessor::FastSubString(ecmaVm, thisString, i, 1);
JSHandle<JSTaggedValue> elementTag(thread, elementString);
// Perform CreateDataProperty(A, "0", S), CreateDataProperty's fast path
JSObject::CreateDataProperty(thread, resultArray, i, elementTag);
ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty can't throw exception");
}
return resultArray.GetTaggedValue();
}
JSTaggedValue BuiltinsString::CreateArrayBySplitString(JSThread *thread, EcmaVM *ecmaVm,
const JSHandle<EcmaString> &thisString, const JSHandle<EcmaString> &seperatorString,
uint32_t thisLength, uint32_t seperatorLength, uint32_t lim)
{
if (thisLength != 0) {
if (seperatorLength != 0) {
return CreateArrayThisStringAndSeperatorStringAreNotEmpty(
thread, ecmaVm, thisString, seperatorString, thisLength, seperatorLength, lim);
}
return CreateArrayFromString(thread, ecmaVm, thisString, thisLength, lim);
} else {
if (seperatorLength != 0) {
JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(1)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Perform CreateDataProperty(A, "0", S), CreateDataProperty's fast path
JSObject::CreateDataProperty(thread, resultArray, 0, JSHandle<JSTaggedValue>(thisString));
ASSERT_PRINT(!thread->HasPendingException(), "CreateDataProperty(A, \"0\", S) can't throw exception");
return resultArray.GetTaggedValue();
}
JSHandle<JSObject> resultArray(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return resultArray.GetTaggedValue();
}
}
JSTaggedValue BuiltinsString::CreateArrayThisStringAndSeperatorStringAreNotEmpty(JSThread *thread,
EcmaVM *ecmaVm, const JSHandle<EcmaString> &thisString, const JSHandle<EcmaString> &seperatorString,
uint32_t thisLength, uint32_t seperatorLength, uint32_t lim)
{
if (lim == UINT32_MAX - 1) {
JSHandle<StringSplitResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetStringSplitResultCache());
JSTaggedValue cacheResult = StringSplitResultCache::FindCachedResult(thread, cacheTable, thisString,
seperatorString);
if (cacheResult != JSTaggedValue::Undefined()) {
JSHandle<JSTaggedValue> resultArray(JSArray::CreateArrayFromList(thread,
JSHandle<TaggedArray>(thread, cacheResult)));
return resultArray.GetTaggedValue();
}
}
uint32_t arrayLength = 0;
std::vector<int32_t> posArray;
int32_t index = 0;
int32_t pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, seperatorString);
while (pos != -1) {
posArray.emplace_back(pos);
++arrayLength;
if (arrayLength == lim) {
break;
}
index = pos + static_cast<int32_t>(seperatorLength);
pos = EcmaStringAccessor::IndexOf(ecmaVm, thisString, seperatorString, index);
}
uint32_t posArrLength = posArray.size();
arrayLength = lim > posArrLength ? posArrLength + 1 : posArrLength;
return JSArray::ArrayCreateWithInit(thread, arrayLength,
[thread, ecmaVm, &thisString, &seperatorString, &posArray, thisLength, seperatorLength, lim, posArrLength]
(const JSHandle<TaggedArray> &newElements, [[maybe_unused]] uint32_t length) {
int32_t index = 0;
int32_t pos = 0;
JSMutableHandle<JSTaggedValue> elementTag(thread, JSTaggedValue::Undefined());
for (uint32_t i = 0; i < posArrLength; i++) {
pos = posArray[i];
EcmaString *elementString = EcmaStringAccessor::GetSubString(ecmaVm, thisString, index, pos - index);
elementTag.Update(JSTaggedValue(elementString));
newElements->Set(thread, i, elementTag);
index = pos + static_cast<int32_t>(seperatorLength);
}
if (lim > posArrLength) {
EcmaString *elementString =
EcmaStringAccessor::GetSubString(ecmaVm, thisString, index, thisLength - index);
elementTag.Update(JSTaggedValue(elementString));
newElements->Set(thread, posArrLength, elementTag);
}
if (lim == UINT32_MAX - 1) {
JSHandle<StringSplitResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetStringSplitResultCache());
StringSplitResultCache::SetCachedResult(thread, cacheTable, thisString, seperatorString, newElements);
}
});
}
// 21.1.3.18
JSTaggedValue BuiltinsString::StartsWith(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, StartsWith);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> searchTag = BuiltinsString::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
bool isRegexp = JSObject::IsRegExp(thread, searchTag);
if (isRegexp) {
THROW_TYPE_ERROR_AND_RETURN(thread, "is regexp", JSTaggedValue::Exception());
}
JSHandle<EcmaString> searchHandle = JSTaggedValue::ToString(thread, searchTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
uint32_t thisLen = EcmaStringAccessor(thisHandle).GetLength();
uint32_t searchLen = EcmaStringAccessor(searchHandle).GetLength();
int32_t pos = 0;
JSHandle<JSTaggedValue> posTag = BuiltinsString::GetCallArg(argv, 1);
if (posTag->IsUndefined()) {
pos = 0;
} else if (posTag->IsInt()) {
pos = posTag->GetInt();
} else {
JSTaggedNumber posVal = JSTaggedValue::ToInteger(thread, posTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
pos = posVal.ToInt32();
}
pos = std::min(std::max(pos, 0), static_cast<int32_t>(thisLen));
if (static_cast<uint32_t>(pos) + searchLen > thisLen) {
return BuiltinsString::GetTaggedBoolean(false);
}
int32_t res = EcmaStringAccessor::IndexOf(thread->GetEcmaVM(), thisHandle, searchHandle, pos);
if (res == pos) {
return BuiltinsString::GetTaggedBoolean(true);
}
return BuiltinsString::GetTaggedBoolean(false);
}
// 21.1.3.19
JSTaggedValue BuiltinsString::Substring(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Substring);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength());
JSHandle<JSTaggedValue> startTag = BuiltinsString::GetCallArg(argv, 0);
JSTaggedNumber startVal = JSTaggedValue::ToInteger(thread, startTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t start = ConvertDoubleToInt(startVal.GetNumber());
int32_t end = 0;
JSHandle<JSTaggedValue> endTag = BuiltinsString::GetCallArg(argv, 1);
if (endTag->IsUndefined()) {
end = thisLen;
} else {
JSTaggedNumber endVal = JSTaggedValue::ToInteger(thread, endTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
end = ConvertDoubleToInt(endVal.GetNumber());
}
start = std::min(std::max(start, 0), thisLen);
end = std::min(std::max(end, 0), thisLen);
int32_t from = std::min(start, end);
int32_t to = std::max(start, end);
int32_t len = to - from;
return JSTaggedValue(EcmaStringAccessor::GetSubString(thread->GetEcmaVM(), thisHandle, from, len));
}
// 21.1.3.20
JSTaggedValue BuiltinsString::ToLocaleLowerCase(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, ToLocaleLowerCase);
JSThread *thread = argv->GetThread();
EcmaVM *ecmaVm = thread->GetEcmaVM();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
// Let O be RequireObjectCoercible(this value).
JSHandle<JSTaggedValue> obj(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let S be ? ToString(O).
JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, obj);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let requestedLocales be ? CanonicalizeLocaleList(locales).
JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
// Fast path
if (locales->IsUndefined() && EcmaStringAccessor(string).IsUtf8()) {
EcmaString *result = EcmaStringAccessor::TryToLower(ecmaVm, string);
return JSTaggedValue(result);
}
JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// If requestedLocales is not an empty List, then Let requestedLocale be requestedLocales[0].
// Else, Let requestedLocale be DefaultLocale().
JSHandle<EcmaString> requestedLocale = intl::LocaleHelper::DefaultLocale(thread);
if (requestedLocales->GetLength() != 0) {
requestedLocale = JSHandle<EcmaString>(thread, requestedLocales->Get(0));
}
// Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences
// removed.
intl::LocaleHelper::ParsedLocale noExtensionsLocale = intl::LocaleHelper::HandleLocale(requestedLocale);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let availableLocales be a List with language tags that includes the languages for which the Unicode Character
// Database contains language sensitive case mappings. Implementations may add additional language tags
// if they support case mapping for additional locales.
std::vector<std::string> availableLocales = intl::LocaleHelper::GetAvailableLocales(thread, nullptr, nullptr);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
std::string locale = intl::LocaleHelper::BestAvailableLocale(availableLocales, noExtensionsLocale.base);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// If locale is undefined, let locale be "und".
if (locale.empty()) {
locale = "und";
}
// Let uString be a List containing in order the code points of S as defined in ES2020, 6.1.4,
// starting at the first element of S.
// Transform those elements in uString to the to the Unicode Default Case Conversion algorithm
icu::Locale icuLocale = icu::Locale::createFromName(locale.c_str());
EcmaString *result = EcmaStringAccessor::ToLocaleLower(ecmaVm, string, icuLocale);
return JSTaggedValue(result);
}
// 21.1.3.21
JSTaggedValue BuiltinsString::ToLocaleUpperCase(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, ToLocaleLowerCase);
JSThread *thread = argv->GetThread();
EcmaVM *ecmaVm = thread->GetEcmaVM();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
// Let O be RequireObjectCoercible(this value).
JSHandle<JSTaggedValue> obj(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let S be ? ToString(O).
JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, obj);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let requestedLocales be ? CanonicalizeLocaleList(locales).
JSHandle<JSTaggedValue> locales = GetCallArg(argv, 0);
JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// If requestedLocales is not an empty List, then Let requestedLocale be requestedLocales[0].
// Else, Let requestedLocale be DefaultLocale().
JSHandle<EcmaString> requestedLocale = intl::LocaleHelper::DefaultLocale(thread);
if (requestedLocales->GetLength() != 0) {
requestedLocale = JSHandle<EcmaString>(thread, requestedLocales->Get(0));
}
// Let noExtensionsLocale be the String value that is requestedLocale with all Unicode locale extension sequences
// removed.
intl::LocaleHelper::ParsedLocale noExtensionsLocale = intl::LocaleHelper::HandleLocale(requestedLocale);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let availableLocales be a List with language tags that includes the languages for which the Unicode Character
// Database contains language sensitive case mappings. Implementations may add additional language tags
// if they support case mapping for additional locales.
std::vector<std::string> availableLocales = intl::LocaleHelper::GetAvailableLocales(thread, nullptr, nullptr);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale).
std::string locale = intl::LocaleHelper::BestAvailableLocale(availableLocales, noExtensionsLocale.base);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// If locale is undefined, let locale be "und".
if (locale.empty()) {
locale = "und";
}
// Let uString be a List containing in order the code points of S as defined in ES2020, 6.1.4,
// starting at the first element of S.
// Transform those elements in uString to the to the Unicode Default Case Conversion algorithm
icu::Locale icuLocale = icu::Locale::createFromName(locale.c_str());
EcmaString *result = EcmaStringAccessor::ToLocaleUpper(ecmaVm, string, icuLocale);
return JSTaggedValue(result);
}
// 21.1.3.22
JSTaggedValue BuiltinsString::ToLowerCase(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, ToLowerCase);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *result = EcmaStringAccessor::ToLower(thread->GetEcmaVM(), thisHandle);
return JSTaggedValue(result);
}
// 21.1.3.23
JSTaggedValue BuiltinsString::ToString(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
return ThisStringValue(argv->GetThread(), GetThis(argv).GetTaggedValue());
}
// 21.1.3.24
JSTaggedValue BuiltinsString::ToUpperCase(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, ToUpperCase);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *result = EcmaStringAccessor::ToUpper(thread->GetEcmaVM(), thisHandle);
return JSTaggedValue(result);
}
// 21.1.3.25
JSTaggedValue BuiltinsString::Trim(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Trim);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM);
return JSTaggedValue(res);
}
JSTaggedValue BuiltinsString::TrimStart(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, TrimStart);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_START);
return JSTaggedValue(res);
}
JSTaggedValue BuiltinsString::TrimEnd(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, TrimEnd);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_END);
return JSTaggedValue(res);
}
JSTaggedValue BuiltinsString::TrimLeft(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, TrimLeft);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_START);
return JSTaggedValue(res);
}
JSTaggedValue BuiltinsString::TrimRight(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, TrimRight);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaString *res = EcmaStringAccessor::Trim(thread, thisHandle, EcmaString::TrimMode::TRIM_END);
return JSTaggedValue(res);
}
// 21.1.3.26
JSTaggedValue BuiltinsString::ValueOf(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
return ThisStringValue(argv->GetThread(), GetThis(argv).GetTaggedValue());
}
// 21.1.3.27
JSTaggedValue BuiltinsString::GetStringIterator(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, GetStringIterator);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
// 1. Let O be RequireObjectCoercible(this value).
JSHandle<JSTaggedValue> current(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// Let S be ToString(O).
JSHandle<EcmaString> string = JSTaggedValue::ToString(thread, current);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(argv->GetThread());
// Return CreateStringIterator(S).
return JSStringIterator::CreateStringIterator(thread, string).GetTaggedValue();
}
// B.2.3.1
JSTaggedValue BuiltinsString::SubStr(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, SubStr);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
// 1. Let O be RequireObjectCoercible(this value).
// 2. Let S be ToString(O).
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisTag);
// 3. ReturnIfAbrupt(S).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> intStart = GetCallArg(argv, 0);
// 4. Let intStart be ToInteger(start).
JSTaggedNumber numStart = JSTaggedValue::ToInteger(thread, intStart);
// 5. ReturnIfAbrupt(intStart).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t start = numStart.ToInt32();
JSHandle<JSTaggedValue> lengthTag = GetCallArg(argv, 1);
// 6. If length is undefined, let end be +; otherwise let end be ToInteger(length).
int32_t end = 0;
if (lengthTag->IsUndefined()) {
end = INT_MAX;
} else {
JSTaggedNumber lengthNumber = JSTaggedValue::ToInteger(thread, lengthTag);
// 7. ReturnIfAbrupt(end).
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
end = lengthNumber.ToInt32();
}
// 8. Let size be the number of code units in S.
int32_t size = static_cast<int32_t>(EcmaStringAccessor(thisString).GetLength());
// 9. If intStart < 0, let intStart be max(size + intStart,0).
if (start < 0) {
start = std::max(size + start, 0);
}
// 10. Let resultLength be min(max(end,0), size intStart).
int32_t resultLength = std::min(std::max(end, 0), size - start);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
// 11. If resultLength  0, return the empty String "".
if (resultLength <= 0) {
return factory->GetEmptyString().GetTaggedValue();
}
return JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), thisString, start, resultLength));
}
// 22.1.3.1
JSTaggedValue BuiltinsString::At(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
BUILTINS_API_TRACE(argv->GetThread(), String, Substring);
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
// 1. Let O be RequireObjectCoercible(this value).
// 2. Let S be ToString(O).
JSHandle<JSTaggedValue> thisTag(JSTaggedValue::RequireObjectCoercible(thread, GetThis(argv)));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// 3. Let len be the length of S.
int32_t thisLen = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength());
// 4. Let relativeIndex be ? ToIntegerOrInfinity(index).
JSHandle<JSTaggedValue> indexTag = BuiltinsString::GetCallArg(argv, 0);
JSTaggedNumber indexVal = JSTaggedValue::ToInteger(thread, indexTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int32_t relativeIndex = ConvertDoubleToInt(indexVal.GetNumber());
// 5. If relativeIndex ≥ 0, then Let k be relativeIndex. 6. Else, Let k be len + relativeIndex.
int32_t k = 0;
if (relativeIndex >= 0) {
k = relativeIndex;
} else {
k = thisLen + relativeIndex;
}
// 7. If k < 0 or k ≥ len, return undefined.
if (k < 0 || k >= thisLen) {
return JSTaggedValue::Undefined();
}
// 8. Return the substring of S from k to k + 1.
return JSTaggedValue(EcmaStringAccessor::FastSubString(thread->GetEcmaVM(), thisHandle, k, 1));
}
JSTaggedValue BuiltinsString::GetLength(EcmaRuntimeCallInfo *argv)
{
ASSERT(argv);
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, String, GetLength);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisHandle = GetThis(argv);
JSHandle<EcmaString> thisString = JSTaggedValue::ToString(thread, thisHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return GetTaggedInt(EcmaStringAccessor(thisString).GetLength());
}
// 21.1.3
JSTaggedValue BuiltinsString::ThisStringValue(JSThread *thread, JSTaggedValue value)
{
BUILTINS_API_TRACE(thread, String, ThisStringValue);
if (value.IsString()) {
return value;
}
if (value.IsECMAObject()) {
auto jshclass = value.GetTaggedObject()->GetClass();
if (jshclass->GetObjectType() == JSType::JS_PRIMITIVE_REF) {
JSTaggedValue primitive = JSPrimitiveRef::Cast(value.GetTaggedObject())->GetValue();
if (primitive.IsString()) {
return primitive;
}
}
}
THROW_TYPE_ERROR_AND_RETURN(thread, "can not convert to String", JSTaggedValue::Exception());
}
JSTaggedValue BuiltinsString::Pad(EcmaRuntimeCallInfo *argv, bool isStart)
{
JSThread *thread = argv->GetThread();
BUILTINS_API_TRACE(thread, String, Pad);
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> thisTag = JSTaggedValue::RequireObjectCoercible(thread, BuiltinsString::GetThis(argv));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<EcmaString> thisHandle = JSTaggedValue::ToString(thread, thisTag);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSTaggedValue> lengthTag = GetCallArg(argv, 0);
int32_t intMaxLength = JSTaggedValue::ToInt32(thread, lengthTag);
int32_t stringLength = static_cast<int32_t>(EcmaStringAccessor(thisHandle).GetLength());
if (intMaxLength <= stringLength) {
return thisHandle.GetTaggedValue();
}
JSHandle<JSTaggedValue> fillString = GetCallArg(argv, 1);
std::u16string stringBuilder;
if (fillString->IsUndefined()) {
stringBuilder = u" ";
} else {
JSHandle<EcmaString> filler = JSTaggedValue::ToString(thread, fillString);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
stringBuilder = EcmaStringAccessor(filler).ToU16String();
}
if (stringBuilder.size() == 0) {
return thisHandle.GetTaggedValue();
}
std::u16string u16strSearch = EcmaStringAccessor(thisHandle).ToU16String();
int32_t fillLen = intMaxLength - stringLength;
int32_t len = static_cast<int32_t>(stringBuilder.length());
std::u16string fiString;
for (int32_t i = 0; i < fillLen; ++i) {
fiString += stringBuilder[i % len];
}
std::u16string resultString;
if (isStart) {
resultString = fiString + u16strSearch;
} else {
resultString = u16strSearch + fiString;
}
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
return factory->NewFromUtf16Literal(reinterpret_cast<const uint16_t *>(resultString.c_str()),
resultString.size()).GetTaggedValue();
}
int32_t BuiltinsString::ConvertDoubleToInt(double d)
{
if (std::isnan(d) || d == -base::POSITIVE_INFINITY) {
return 0;
}
if (d >= static_cast<double>(INT_MAX)) {
return INT_MAX;
}
if (d <= static_cast<double>(INT_MIN)) {
return INT_MIN;
}
return base::NumberHelper::DoubleToInt(d, base::INT32_BITS);
}
JSTaggedValue BuiltinsString::StringToList(JSThread *thread, JSHandle<EcmaString> &str)
{
JSHandle<StringToListResultCache> cacheTable(thread->GetCurrentEcmaContext()->GetStringToListResultCache());
JSTaggedValue cacheResult = StringToListResultCache::FindCachedResult(thread, cacheTable, str);
if (cacheResult != JSTaggedValue::Undefined()) {
JSHandle<JSTaggedValue> resultArray(JSArray::CreateArrayFromList(thread,
JSHandle<TaggedArray>(thread, cacheResult)));
return resultArray.GetTaggedValue();
}
JSTaggedValue newArray = JSArray::ArrayCreate(thread, JSTaggedNumber(0)).GetTaggedValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSObject> newArrayHandle(thread, newArray);
JSHandle<EcmaString> iteratedString(str);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> oldElements(thread, newArrayHandle->GetElements());
uint32_t totalElements = EcmaStringAccessor(iteratedString).GetLength();
JSHandle<TaggedArray> elements = (oldElements->GetLength() < totalElements) ?
factory->ExtendArray(oldElements, totalElements) : oldElements;
uint32_t index = 0;
while (index < totalElements) {
uint16_t c = EcmaStringAccessor(iteratedString).Get(index);
JSHandle<EcmaString> newStr = factory->NewFromUtf16Literal(&c, 1);
elements->Set(thread, index, newStr);
index++;
}
JSHandle<JSArray>(newArrayHandle)->SetArrayLength(thread, totalElements);
newArrayHandle->SetElements(thread, elements);
StringToListResultCache::SetCachedResult(thread, cacheTable, str, elements);
return newArrayHandle.GetTaggedValue();
}
JSTaggedValue StringSplitResultCache::CreateCacheTable(const JSThread *thread)
{
int length = CACHE_SIZE * ENTRY_SIZE;
auto table = static_cast<StringSplitResultCache*>(
*thread->GetEcmaVM()->GetFactory()->NewTaggedArray(length, JSTaggedValue::Undefined()));
return JSTaggedValue(table);
}
JSTaggedValue StringSplitResultCache::FindCachedResult(const JSThread *thread,
const JSHandle<StringSplitResultCache> &cache, const JSHandle<EcmaString> &thisString,
const JSHandle<EcmaString> &pattern)
{
uint32_t hash = EcmaStringAccessor(thisString).GetHashcode();
uint32_t entry = hash & (CACHE_SIZE - 1);
uint32_t index = entry * ENTRY_SIZE;
JSTaggedValue cacheThis = cache->Get(index + STRING_INDEX);
JSTaggedValue cachePattern = cache->Get(index + PATTERN_INDEX);
if (!cacheThis.IsString() || !cachePattern.IsString()) {
return JSTaggedValue::Undefined();
}
JSHandle<EcmaString> cacheStringHandle(thread, cacheThis);
JSHandle<EcmaString> cachePatternHandle(thread, cachePattern);
if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), thisString, cacheStringHandle) &&
EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), pattern, cachePatternHandle)) {
JSHandle<TaggedArray> cacheArray(thread, cache->Get(index + ARRAY_INDEX));
uint32_t arrayLength = cacheArray->GetLength();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> copyArray = factory->NewAndCopyTaggedArray(cacheArray,
arrayLength, arrayLength);
return copyArray.GetTaggedValue();
}
return JSTaggedValue::Undefined();
}
void StringSplitResultCache::SetCachedResult(const JSThread *thread, const JSHandle<StringSplitResultCache> &cache,
const JSHandle<EcmaString> &thisString, const JSHandle<EcmaString> &pattern,
const JSHandle<TaggedArray> &resultArray)
{
// clone to cache array
uint32_t arrayLength = resultArray->GetLength();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newElements(factory->NewTaggedArray(arrayLength));
for (uint32_t i = 0; i < arrayLength; i++) {
newElements->Set(thread, i, resultArray->Get(i));
}
uint32_t hash = EcmaStringAccessor(thisString).GetHashcode();
uint32_t entry = hash & (CACHE_SIZE - 1);
uint32_t index = entry * ENTRY_SIZE;
cache->Set(thread, index + STRING_INDEX, thisString);
cache->Set(thread, index + PATTERN_INDEX, pattern);
cache->Set(thread, index + ARRAY_INDEX, newElements);
}
JSTaggedValue StringToListResultCache::CreateCacheTable(const JSThread *thread)
{
int length = CACHE_SIZE * ENTRY_SIZE;
auto table = static_cast<StringToListResultCache*>(
*thread->GetEcmaVM()->GetFactory()->NewTaggedArray(length, JSTaggedValue::Undefined()));
return JSTaggedValue(table);
}
JSTaggedValue StringToListResultCache::FindCachedResult(const JSThread *thread,
const JSHandle<StringToListResultCache> &cache, const JSHandle<EcmaString> &thisString)
{
if (EcmaStringAccessor(thisString).GetLength() > MAX_STRING_LENGTH) {
return JSTaggedValue::Undefined();
}
uint32_t hash = EcmaStringAccessor(thisString).GetHashcode();
uint32_t entry = hash & (CACHE_SIZE - 1);
uint32_t index = entry * ENTRY_SIZE;
JSHandle<JSTaggedValue> cacheThis(thread, cache->Get(index + STRING_INDEX));
if (!cacheThis->IsString()) {
return JSTaggedValue::Undefined();
}
JSHandle<EcmaString> cacheStr(cacheThis);
if (EcmaStringAccessor::StringsAreEqual(thread->GetEcmaVM(), thisString, cacheStr)) {
return cache->Get(index + ARRAY_INDEX);
}
return JSTaggedValue::Undefined();
}
void StringToListResultCache::SetCachedResult(const JSThread *thread, const JSHandle<StringToListResultCache> &cache,
const JSHandle<EcmaString> &thisString, const JSHandle<TaggedArray> &resultArray)
{
if (EcmaStringAccessor(thisString).GetLength() > MAX_STRING_LENGTH ||
EcmaStringAccessor(thisString).GetLength() == 0) {
return;
}
if (!EcmaStringAccessor(thisString).IsInternString()) {
return;
}
// clone to cache array
uint32_t arrayLength = resultArray->GetLength();
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> newElements(factory->NewCOWTaggedArray(arrayLength));
for (uint32_t i = 0; i < arrayLength; i++) {
newElements->Set(thread, i, resultArray->Get(i));
}
uint32_t hash = EcmaStringAccessor(thisString).GetHashcode();
uint32_t entry = hash & (CACHE_SIZE - 1);
uint32_t index = entry * ENTRY_SIZE;
cache->Set(thread, index + STRING_INDEX, thisString);
cache->Set(thread, index + ARRAY_INDEX, newElements);
}
} // namespace panda::ecmascript::builtins