mirror of
https://gitee.com/openharmony/arkcompiler_ets_runtime
synced 2024-11-27 20:20:52 +00:00
180e0a3f87
Eliminates circular header dependency in some fundamental headers like js_thread.h, js_tagged_value.h, etc. Issue: https://gitee.com/openharmony/arkcompiler_ets_runtime/issues/I7ZPL4 Signed-off-by: Onlynagesha <orina_zju@163.com> Change-Id: I5a78073992b77643718abdc1e0813485c0e439bb
432 lines
20 KiB
C++
432 lines
20 KiB
C++
/*
|
|
* Copyright (c) 2022 Huawei Device Co., Ltd.
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "ecmascript/js_list_format.h"
|
|
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "ecmascript/intl/locale_helper.h"
|
|
#include "ecmascript/ecma_macros.h"
|
|
#include "ecmascript/global_env.h"
|
|
#include "ecmascript/global_env_constants.h"
|
|
#include "ecmascript/js_array.h"
|
|
#include "ecmascript/js_locale.h"
|
|
#include "ecmascript/js_iterator.h"
|
|
#include "ecmascript/object_factory-inl.h"
|
|
|
|
#include "unicode/fieldpos.h"
|
|
#include "unicode/fpositer.h"
|
|
#include "unicode/formattedvalue.h"
|
|
#include "unicode/stringpiece.h"
|
|
#include "unicode/unistr.h"
|
|
#include "unicode/utf8.h"
|
|
#include "unicode/uloc.h"
|
|
#include "unicode/ustring.h"
|
|
|
|
namespace panda::ecmascript {
|
|
icu::ListFormatter *JSListFormat::GetIcuListFormatter() const
|
|
{
|
|
ASSERT(GetIcuLF().IsJSNativePointer());
|
|
auto result = JSNativePointer::Cast(GetIcuLF().GetTaggedObject())->GetExternalPointer();
|
|
return reinterpret_cast<icu::ListFormatter *>(result);
|
|
}
|
|
|
|
void JSListFormat::FreeIcuListFormatter(void *pointer, [[maybe_unused]] void* hint)
|
|
{
|
|
if (pointer == nullptr) {
|
|
return;
|
|
}
|
|
auto icuListFormat = reinterpret_cast<icu::ListFormatter *>(pointer);
|
|
icuListFormat->~ListFormatter();
|
|
delete icuListFormat;
|
|
}
|
|
|
|
void JSListFormat::SetIcuListFormatter(JSThread *thread, const JSHandle<JSListFormat> listFormat,
|
|
icu::ListFormatter *icuListFormatter, const DeleteEntryPoint &callback)
|
|
{
|
|
EcmaVM *ecmaVm = thread->GetEcmaVM();
|
|
ObjectFactory *factory = ecmaVm->GetFactory();
|
|
ASSERT(icuListFormatter != nullptr);
|
|
JSTaggedValue data = listFormat->GetIcuLF();
|
|
if (data.IsJSNativePointer()) {
|
|
JSNativePointer *native = JSNativePointer::Cast(data.GetTaggedObject());
|
|
native->ResetExternalPointer(icuListFormatter);
|
|
return;
|
|
}
|
|
JSHandle<JSNativePointer> pointer = factory->NewJSNativePointer(icuListFormatter, callback);
|
|
listFormat->SetIcuLF(thread, pointer.GetTaggedValue());
|
|
}
|
|
|
|
JSHandle<TaggedArray> JSListFormat::GetAvailableLocales(JSThread *thread)
|
|
{
|
|
JSHandle<GlobalEnv> env = thread->GetEcmaVM()->GetGlobalEnv();
|
|
JSHandle<JSTaggedValue> listFormatLocales = env->GetListFormatLocales();
|
|
if (!listFormatLocales->IsUndefined()) {
|
|
return JSHandle<TaggedArray>::Cast(listFormatLocales);
|
|
}
|
|
const char *key = "listPattern";
|
|
const char *path = nullptr;
|
|
std::vector<std::string> availableStringLocales = intl::LocaleHelper::GetAvailableLocales(thread, key, path);
|
|
JSHandle<TaggedArray> availableLocales = JSLocale::ConstructLocaleList(thread, availableStringLocales);
|
|
env->SetListFormatLocales(thread, availableLocales);
|
|
return availableLocales;
|
|
}
|
|
|
|
// 13. InitializeListFormat ( listformat, locales, options )
|
|
JSHandle<JSListFormat> JSListFormat::InitializeListFormat(JSThread *thread,
|
|
const JSHandle<JSListFormat> &listFormat,
|
|
const JSHandle<JSTaggedValue> &locales,
|
|
const JSHandle<JSTaggedValue> &options)
|
|
{
|
|
[[maybe_unused]] EcmaHandleScope scope(thread);
|
|
EcmaVM *ecmaVm = thread->GetEcmaVM();
|
|
ObjectFactory *factory = ecmaVm->GetFactory();
|
|
auto globalConst = thread->GlobalConstants();
|
|
|
|
// 3. Let requestedLocales be ? CanonicalizeLocaleList(locales).
|
|
JSHandle<TaggedArray> requestedLocales = intl::LocaleHelper::CanonicalizeLocaleList(thread, locales);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
|
|
|
|
// 4. Let options be ? GetOptionsObject(options).
|
|
JSHandle<JSObject> optionsObject;
|
|
if (options->IsUndefined()) {
|
|
optionsObject = factory->CreateNullJSObject();
|
|
} else {
|
|
optionsObject = JSTaggedValue::ToObject(thread, options);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
|
|
}
|
|
|
|
// 5. Let opt be a new Record.
|
|
// 6. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
|
|
JSHandle<JSTaggedValue> property = globalConst->GetHandledLocaleMatcherString();
|
|
auto matcher = JSLocale::GetOptionOfString<LocaleMatcherOption>(
|
|
thread, optionsObject, property, {LocaleMatcherOption::LOOKUP, LocaleMatcherOption::BEST_FIT},
|
|
{"lookup", "best fit"}, LocaleMatcherOption::BEST_FIT);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
|
|
|
|
// 8. Let localeData be %ListFormat%.[[LocaleData]].
|
|
JSHandle<TaggedArray> availableLocales;
|
|
if (requestedLocales->GetLength() == 0) {
|
|
availableLocales = factory->EmptyArray();
|
|
} else {
|
|
availableLocales = GetAvailableLocales(thread);
|
|
}
|
|
|
|
// 9. Let r be ResolveLocale(%ListFormat%.[[AvailableLocales]], requestedLocales,
|
|
// opt, %ListFormat%.[[RelevantExtensionKeys]], localeData).
|
|
std::set<std::string> relevantExtensionKeys {""};
|
|
ResolvedLocale r =
|
|
JSLocale::ResolveLocale(thread, availableLocales, requestedLocales, matcher, relevantExtensionKeys);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
|
|
|
|
// 10. Set listFormat.[[Locale]] to r.[[locale]].
|
|
icu::Locale icuLocale = r.localeData;
|
|
JSHandle<EcmaString> localeStr = intl::LocaleHelper::ToLanguageTag(thread, icuLocale);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
|
|
listFormat->SetLocale(thread, localeStr.GetTaggedValue());
|
|
|
|
// 11. Let type be ? GetOption(options, "type", "string", « "conjunction", "disjunction", "unit" », "conjunction").
|
|
property = globalConst->GetHandledTypeString();
|
|
auto type = JSLocale::GetOptionOfString<ListTypeOption>(thread, optionsObject, property,
|
|
{ListTypeOption::CONJUNCTION, ListTypeOption::DISJUNCTION,
|
|
ListTypeOption::UNIT},
|
|
{"conjunction", "disjunction", "unit"},
|
|
ListTypeOption::CONJUNCTION);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
|
|
|
|
// 12. Set listFormat.[[Type]] to type.
|
|
listFormat->SetType(type);
|
|
|
|
// 13. Let style be ? GetOption(options, "style", "string", « "long", "short", "narrow" », "long").
|
|
property = globalConst->GetHandledStyleString();
|
|
auto style = JSLocale::GetOptionOfString<ListStyleOption>(thread, optionsObject, property,
|
|
{ListStyleOption::LONG, ListStyleOption::SHORT,
|
|
ListStyleOption::NARROW},
|
|
{"long", "short", "narrow"}, ListStyleOption::LONG);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSListFormat, thread);
|
|
|
|
// 14. Set listFormat.[[Style]] to style.
|
|
listFormat->SetStyle(style);
|
|
|
|
// 15. Let dataLocale be r.[[dataLocale]].
|
|
// 16. Let dataLocaleData be localeData.[[<dataLocale>]].
|
|
// 17. Let dataLocaleTypes be dataLocaleData.[[<type>]].
|
|
// 18. Set listFormat.[[Templates]] to dataLocaleTypes.[[<style>]].
|
|
// 19. Return listFormat.
|
|
|
|
// Trans typeOption to ICU type
|
|
UListFormatterType uType;
|
|
switch (type) {
|
|
case ListTypeOption::CONJUNCTION:
|
|
uType = ULISTFMT_TYPE_AND;
|
|
break;
|
|
case ListTypeOption::DISJUNCTION:
|
|
uType = ULISTFMT_TYPE_OR;
|
|
break;
|
|
case ListTypeOption::UNIT:
|
|
uType = ULISTFMT_TYPE_UNITS;
|
|
break;
|
|
default:
|
|
LOG_ECMA(FATAL) << "this branch is unreachable";
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// Trans StyleOption to ICU Style
|
|
UListFormatterWidth uStyle;
|
|
switch (style) {
|
|
case ListStyleOption::LONG:
|
|
uStyle = ULISTFMT_WIDTH_WIDE;
|
|
break;
|
|
case ListStyleOption::SHORT:
|
|
uStyle = ULISTFMT_WIDTH_SHORT;
|
|
break;
|
|
case ListStyleOption::NARROW:
|
|
uStyle = ULISTFMT_WIDTH_NARROW;
|
|
break;
|
|
default:
|
|
LOG_ECMA(FATAL) << "this branch is unreachable";
|
|
UNREACHABLE();
|
|
}
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
icu::ListFormatter *icuListFormatter = icu::ListFormatter::createInstance(icuLocale, uType, uStyle, status);
|
|
if (U_FAILURE(status) || icuListFormatter == nullptr) {
|
|
delete icuListFormatter;
|
|
if (status == UErrorCode::U_MISSING_RESOURCE_ERROR) {
|
|
THROW_REFERENCE_ERROR_AND_RETURN(thread, "can not find icu data resources", listFormat);
|
|
}
|
|
THROW_RANGE_ERROR_AND_RETURN(thread, "create icu::ListFormatter failed", listFormat);
|
|
}
|
|
SetIcuListFormatter(thread, listFormat, icuListFormatter, JSListFormat::FreeIcuListFormatter);
|
|
return listFormat;
|
|
}
|
|
|
|
// 13.1.5 StringListFromIterable ( iterable )
|
|
JSHandle<JSTaggedValue> JSListFormat::StringListFromIterable(JSThread *thread, const JSHandle<JSTaggedValue> &iterable)
|
|
{
|
|
JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
|
|
JSHandle<JSTaggedValue> arrayList = JSHandle<JSTaggedValue>::Cast(array);
|
|
// 1. If iterable is undefined, then
|
|
// a. Return a new empty List.
|
|
if (iterable->IsUndefined()) {
|
|
return arrayList;
|
|
}
|
|
// 2. Let iteratorRecord be ? GetIterator(iterable).
|
|
JSHandle<JSTaggedValue> iteratorRecord(JSIterator::GetIterator(thread, iterable));
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
|
|
// 3. Let list be a new empty List.
|
|
// 4. Let next be true.
|
|
JSHandle<JSTaggedValue> next(thread, JSTaggedValue::True());
|
|
// 5. Repeat, while next is not false,
|
|
// a. Set next to ? IteratorStep(iteratorRecord).
|
|
// b. If next is not false, then
|
|
// i. Let nextValue be ? IteratorValue(next).
|
|
// ii. If Type(nextValue) is not String, then
|
|
// 1. Let error be ThrowCompletion(a newly created TypeError object).
|
|
// 2. Return ? IteratorClose(iteratorRecord, error).
|
|
// iii. Append nextValue to the end of the List list.
|
|
uint32_t k = 0;
|
|
while (!next->IsFalse()) {
|
|
next = JSIterator::IteratorStep(thread, iteratorRecord);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
|
|
if (!next->IsFalse()) {
|
|
JSHandle<JSTaggedValue> nextValue(JSIterator::IteratorValue(thread, next));
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
|
|
if (!nextValue->IsString()) {
|
|
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
|
|
JSHandle<JSObject> typeError = factory->GetJSError(ErrorType::TYPE_ERROR, "nextValue is not string");
|
|
JSHandle<JSTaggedValue> error(
|
|
factory->NewCompletionRecord(CompletionRecordType::THROW, JSHandle<JSTaggedValue>(typeError)));
|
|
JSTaggedValue result = JSIterator::IteratorClose(thread, iteratorRecord, error).GetTaggedValue();
|
|
THROW_TYPE_ERROR_AND_RETURN(thread, "type error", JSHandle<JSTaggedValue>(thread, result));
|
|
}
|
|
JSArray::FastSetPropertyByValue(thread, arrayList, k, nextValue);
|
|
k++;
|
|
}
|
|
}
|
|
// 6. Return list.
|
|
return arrayList;
|
|
}
|
|
|
|
namespace {
|
|
std::vector<icu::UnicodeString> ToUnicodeStringArray(JSThread *thread, const JSHandle<JSArray> &array)
|
|
{
|
|
uint32_t length = array->GetArrayLength();
|
|
std::vector<icu::UnicodeString> result;
|
|
for (uint32_t k = 0; k < length; k++) {
|
|
JSHandle<JSTaggedValue> listArray = JSHandle<JSTaggedValue>::Cast(array);
|
|
JSHandle<JSTaggedValue> kValue = JSArray::FastGetPropertyByValue(thread, listArray, k);
|
|
ASSERT(kValue->IsString());
|
|
JSHandle<EcmaString> kValueString = JSTaggedValue::ToString(thread, kValue);
|
|
std::string stdString = intl::LocaleHelper::ConvertToStdString(kValueString);
|
|
icu::StringPiece sp(stdString);
|
|
icu::UnicodeString uString = icu::UnicodeString::fromUTF8(sp);
|
|
result.push_back(uString);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
icu::FormattedList GetIcuFormatted(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
|
|
const JSHandle<JSArray> &listArray)
|
|
{
|
|
icu::ListFormatter *icuListFormat = listFormat->GetIcuListFormatter();
|
|
ASSERT(icuListFormat != nullptr);
|
|
std::vector<icu::UnicodeString> usArray = ToUnicodeStringArray(thread, listArray);
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
icu::FormattedList formatted = icuListFormat->formatStringsToValue(usArray.data(),
|
|
static_cast<int32_t>(usArray.size()),
|
|
status);
|
|
return formatted;
|
|
}
|
|
|
|
void FormatListToArray(JSThread *thread, const icu::FormattedList &formatted, const JSHandle<JSArray> &receiver,
|
|
UErrorCode &status, icu::UnicodeString &listString)
|
|
{
|
|
icu::ConstrainedFieldPosition cfpo;
|
|
cfpo.constrainCategory(UFIELD_CATEGORY_LIST);
|
|
auto globalConst = thread->GlobalConstants();
|
|
JSMutableHandle<JSTaggedValue> typeString(thread, JSTaggedValue::Undefined());
|
|
int index = 0;
|
|
while (formatted.nextPosition(cfpo, status) && U_SUCCESS(status)) {
|
|
int32_t fieldId = cfpo.getField();
|
|
int32_t start = cfpo.getStart();
|
|
int32_t limit = cfpo.getLimit();
|
|
if (static_cast<UListFormatterField>(fieldId) == ULISTFMT_ELEMENT_FIELD) {
|
|
JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, listString, start, limit);
|
|
typeString.Update(globalConst->GetElementString());
|
|
JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
|
|
RETURN_IF_ABRUPT_COMPLETION(thread);
|
|
index++;
|
|
} else {
|
|
JSHandle<EcmaString> substring = intl::LocaleHelper::UStringToString(thread, listString, start, limit);
|
|
typeString.Update(globalConst->GetLiteralString());
|
|
JSLocale::PutElement(thread, index, receiver, typeString, JSHandle<JSTaggedValue>::Cast(substring));
|
|
RETURN_IF_ABRUPT_COMPLETION(thread);
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
JSHandle<JSTaggedValue> ListOptionStyleToEcmaString(JSThread *thread, ListStyleOption style)
|
|
{
|
|
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
|
|
auto globalConst = thread->GlobalConstants();
|
|
switch (style) {
|
|
case ListStyleOption::LONG:
|
|
result.Update(globalConst->GetHandledLongString().GetTaggedValue());
|
|
break;
|
|
case ListStyleOption::SHORT:
|
|
result.Update(globalConst->GetHandledShortString().GetTaggedValue());
|
|
break;
|
|
case ListStyleOption::NARROW:
|
|
result.Update(globalConst->GetHandledNarrowString().GetTaggedValue());
|
|
break;
|
|
default:
|
|
LOG_ECMA(FATAL) << "this branch is unreachable";
|
|
UNREACHABLE();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
JSHandle<JSTaggedValue> ListOptionTypeToEcmaString(JSThread *thread, ListTypeOption type)
|
|
{
|
|
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Undefined());
|
|
auto globalConst = thread->GlobalConstants();
|
|
switch (type) {
|
|
case ListTypeOption::CONJUNCTION:
|
|
result.Update(globalConst->GetHandledConjunctionString().GetTaggedValue());
|
|
break;
|
|
case ListTypeOption::DISJUNCTION:
|
|
result.Update(globalConst->GetHandledDisjunctionString().GetTaggedValue());
|
|
break;
|
|
case ListTypeOption::UNIT:
|
|
result.Update(globalConst->GetHandledUnitString().GetTaggedValue());
|
|
break;
|
|
default:
|
|
LOG_ECMA(FATAL) << "this branch is unreachable";
|
|
UNREACHABLE();
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// 13.1.3 FormatList ( listFormat, list )
|
|
JSHandle<EcmaString> JSListFormat::FormatList(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
|
|
const JSHandle<JSArray> &listArray)
|
|
{
|
|
JSHandle<EcmaString> stringValue;
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray);
|
|
if (U_FAILURE(status)) {
|
|
THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", stringValue);
|
|
}
|
|
icu::UnicodeString result = formatted.toString(status);
|
|
if (U_FAILURE(status)) {
|
|
THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", stringValue);
|
|
}
|
|
stringValue = intl::LocaleHelper::UStringToString(thread, result);
|
|
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, stringValue);
|
|
// 4. Return result
|
|
return stringValue;
|
|
}
|
|
|
|
// 13.1.4 FormatListToParts ( listFormat, list )
|
|
JSHandle<JSArray> JSListFormat::FormatListToParts(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
|
|
const JSHandle<JSArray> &listArray)
|
|
{
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
icu::FormattedList formatted = GetIcuFormatted(thread, listFormat, listArray);
|
|
if (U_FAILURE(status)) {
|
|
THROW_RANGE_ERROR_AND_RETURN(thread, "icu listformat failed", listArray);
|
|
}
|
|
icu::UnicodeString result = formatted.toString(status);
|
|
if (U_FAILURE(status)) {
|
|
THROW_RANGE_ERROR_AND_RETURN(thread, "formatted list toString failed", listArray);
|
|
}
|
|
JSHandle<JSArray> array = JSHandle<JSArray>::Cast(JSArray::ArrayCreate(thread, JSTaggedNumber(0)));
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
|
|
FormatListToArray(thread, formatted, array, status, result);
|
|
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSArray, thread);
|
|
return array;
|
|
}
|
|
|
|
void JSListFormat::ResolvedOptions(JSThread *thread, const JSHandle<JSListFormat> &listFormat,
|
|
const JSHandle<JSObject> &options)
|
|
{
|
|
auto globalConst = thread->GlobalConstants();
|
|
|
|
// [[Locale]]
|
|
JSHandle<JSTaggedValue> propertyKey = globalConst->GetHandledLocaleString();
|
|
JSHandle<JSTaggedValue> locale(thread, listFormat->GetLocale());
|
|
JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, locale);
|
|
RETURN_IF_ABRUPT_COMPLETION(thread);
|
|
|
|
// [[type]]
|
|
ListTypeOption type = listFormat->GetType();
|
|
propertyKey = globalConst->GetHandledTypeString();
|
|
JSHandle<JSTaggedValue> typeString = ListOptionTypeToEcmaString(thread, type);
|
|
JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, typeString);
|
|
RETURN_IF_ABRUPT_COMPLETION(thread);
|
|
|
|
// [[Style]]
|
|
ListStyleOption style = listFormat->GetStyle();
|
|
propertyKey = globalConst->GetHandledStyleString();
|
|
JSHandle<JSTaggedValue> styleString = ListOptionStyleToEcmaString(thread, style);
|
|
JSObject::CreateDataPropertyOrThrow(thread, options, propertyKey, styleString);
|
|
RETURN_IF_ABRUPT_COMPLETION(thread);
|
|
}
|
|
} // namespace panda::ecmascript
|