5b14ee115a
_StringBase is used everywhere as if it was assignable to String, however it itself does not implement String, its subclasses do. This change addresses this. Also removes unused native runtime method from one of subclasses. Bug: https://github.com/dart-lang/sdk/issues/31052 Change-Id: Iea2389d1b48a3689fb11c99c0e230747ab1c5962 Reviewed-on: https://dart-review.googlesource.com/12444 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Vyacheslav Egorov <vegorov@google.com>
594 lines
21 KiB
C++
594 lines
21 KiB
C++
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
#include "vm/bootstrap_natives.h"
|
|
|
|
#include "include/dart_api.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#include "vm/exceptions.h"
|
|
#include "vm/isolate.h"
|
|
#include "vm/native_entry.h"
|
|
#include "vm/object.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/unicode.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_NATIVE_ENTRY(String_fromEnvironment, 3) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, name, arguments->NativeArgAt(1));
|
|
GET_NATIVE_ARGUMENT(String, default_value, arguments->NativeArgAt(2));
|
|
// Call the embedder to supply us with the environment.
|
|
const String& env_value =
|
|
String::Handle(Api::GetEnvironmentValue(thread, name));
|
|
if (!env_value.IsNull()) {
|
|
return Symbols::New(thread, env_value);
|
|
}
|
|
return default_value.raw();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(StringBase_createFromCodePoints, 3) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, list, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
|
|
|
|
Array& a = Array::Handle();
|
|
intptr_t length;
|
|
if (list.IsGrowableObjectArray()) {
|
|
const GrowableObjectArray& growableArray = GrowableObjectArray::Cast(list);
|
|
a ^= growableArray.data();
|
|
length = growableArray.Length();
|
|
} else if (list.IsArray()) {
|
|
a ^= Array::Cast(list).raw();
|
|
length = a.Length();
|
|
} else {
|
|
Exceptions::ThrowArgumentError(list);
|
|
return NULL; // Unreachable.
|
|
}
|
|
|
|
intptr_t start = start_obj.Value();
|
|
if ((start < 0) || (start > length)) {
|
|
Exceptions::ThrowArgumentError(start_obj);
|
|
}
|
|
|
|
intptr_t end = end_obj.Value();
|
|
if ((end < start) || (end > length)) {
|
|
Exceptions::ThrowArgumentError(end_obj);
|
|
}
|
|
|
|
// Unbox the array and determine the maximum element width.
|
|
bool is_one_byte_string = true;
|
|
intptr_t array_len = end - start;
|
|
intptr_t utf16_len = array_len;
|
|
int32_t* utf32_array = zone->Alloc<int32_t>(array_len);
|
|
Instance& index_object = Instance::Handle(zone);
|
|
for (intptr_t i = 0; i < array_len; i++) {
|
|
index_object ^= a.At(start + i);
|
|
if (!index_object.IsSmi()) {
|
|
Exceptions::ThrowArgumentError(index_object);
|
|
}
|
|
intptr_t value = Smi::Cast(index_object).Value();
|
|
if (Utf::IsOutOfRange(value)) {
|
|
Exceptions::ThrowByType(Exceptions::kArgument, Object::empty_array());
|
|
UNREACHABLE();
|
|
}
|
|
// Now it is safe to cast the value.
|
|
int32_t value32 = static_cast<int32_t>(value);
|
|
if (!Utf::IsLatin1(value32)) {
|
|
is_one_byte_string = false;
|
|
if (Utf::IsSupplementary(value32)) {
|
|
utf16_len += 1;
|
|
}
|
|
}
|
|
utf32_array[i] = value32;
|
|
}
|
|
if (is_one_byte_string) {
|
|
return OneByteString::New(utf32_array, array_len, Heap::kNew);
|
|
}
|
|
return TwoByteString::New(utf16_len, utf32_array, array_len, Heap::kNew);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(StringBase_substringUnchecked, 3) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
|
|
|
|
intptr_t start = start_obj.Value();
|
|
intptr_t end = end_obj.Value();
|
|
return String::SubString(receiver, start, (end - start));
|
|
}
|
|
|
|
// Return the bitwise-or of all characters in the slice from start to end.
|
|
static uint16_t CharacterLimit(const String& string,
|
|
intptr_t start,
|
|
intptr_t end) {
|
|
ASSERT(string.IsTwoByteString() || string.IsExternalTwoByteString());
|
|
// Maybe do loop unrolling, and handle two uint16_t in a single uint32_t
|
|
// operation.
|
|
NoSafepointScope no_safepoint;
|
|
uint16_t result = 0;
|
|
if (string.IsTwoByteString()) {
|
|
for (intptr_t i = start; i < end; i++) {
|
|
result |= TwoByteString::CharAt(string, i);
|
|
}
|
|
} else {
|
|
for (intptr_t i = start; i < end; i++) {
|
|
result |= ExternalTwoByteString::CharAt(string, i);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static const intptr_t kLengthSize = 11;
|
|
static const intptr_t kLengthMask = (1 << kLengthSize) - 1;
|
|
|
|
static bool CheckSlicesOneByte(const String& base,
|
|
const Array& matches,
|
|
const int len) {
|
|
Instance& object = Instance::Handle();
|
|
// Check each slice for one-bytedness.
|
|
for (intptr_t i = 0; i < len; i++) {
|
|
object ^= matches.At(i);
|
|
if (object.IsSmi()) {
|
|
intptr_t slice_start = Smi::Cast(object).Value();
|
|
intptr_t slice_end;
|
|
if (slice_start < 0) {
|
|
intptr_t bits = -slice_start;
|
|
slice_start = bits >> kLengthSize;
|
|
slice_end = slice_start + (bits & kLengthMask);
|
|
} else {
|
|
i++;
|
|
if (i >= len) {
|
|
// Bad format, handled later.
|
|
return false;
|
|
}
|
|
object ^= matches.At(i);
|
|
if (!object.IsSmi()) {
|
|
// Bad format, handled later.
|
|
return false;
|
|
}
|
|
slice_end = Smi::Cast(object).Value();
|
|
}
|
|
uint16_t char_limit = CharacterLimit(base, slice_start, slice_end);
|
|
if (char_limit > 0xff) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(StringBase_joinReplaceAllResult, 4) {
|
|
const String& base = String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(GrowableObjectArray, matches_growable,
|
|
arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, length_obj, arguments->NativeArgAt(2));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, is_onebyte_obj, arguments->NativeArgAt(3));
|
|
|
|
intptr_t len = matches_growable.Length();
|
|
const Array& matches = Array::Handle(zone, matches_growable.data());
|
|
|
|
const intptr_t length = length_obj.Value();
|
|
if (length < 0) {
|
|
Exceptions::ThrowArgumentError(length_obj);
|
|
}
|
|
|
|
// Start out assuming result is one-byte if replacements are.
|
|
bool is_onebyte = is_onebyte_obj.value();
|
|
if (is_onebyte) {
|
|
// If any of the base string slices are not one-byte, the result will be
|
|
// a two-byte string.
|
|
if (!base.IsOneByteString() && !base.IsExternalOneByteString()) {
|
|
is_onebyte = CheckSlicesOneByte(base, matches, len);
|
|
}
|
|
}
|
|
|
|
const intptr_t base_length = base.Length();
|
|
String& result = String::Handle(zone);
|
|
if (is_onebyte) {
|
|
result ^= OneByteString::New(length, Heap::kNew);
|
|
} else {
|
|
result ^= TwoByteString::New(length, Heap::kNew);
|
|
}
|
|
Instance& object = Instance::Handle(zone);
|
|
intptr_t write_index = 0;
|
|
for (intptr_t i = 0; i < len; i++) {
|
|
object ^= matches.At(i);
|
|
if (object.IsSmi()) {
|
|
intptr_t slice_start = Smi::Cast(object).Value();
|
|
intptr_t slice_length = -1;
|
|
// Slices with limited ranges are stored in a single negative Smi.
|
|
if (slice_start < 0) {
|
|
intptr_t bits = -slice_start;
|
|
slice_start = bits >> kLengthSize;
|
|
slice_length = bits & kLengthMask;
|
|
} else {
|
|
i++;
|
|
if (i < len) { // Otherwise slice_length stays at -1.
|
|
object ^= matches.At(i);
|
|
if (object.IsSmi()) {
|
|
intptr_t slice_end = Smi::Cast(object).Value();
|
|
slice_length = slice_end - slice_start;
|
|
}
|
|
}
|
|
}
|
|
if (slice_length > 0) {
|
|
if (0 <= slice_start && slice_start + slice_length <= base_length &&
|
|
write_index + slice_length <= length) {
|
|
String::Copy(result, write_index, base, slice_start, slice_length);
|
|
write_index += slice_length;
|
|
continue;
|
|
}
|
|
}
|
|
// Either the slice_length was zero,
|
|
// or the first smi was positive and not followed by another smi,
|
|
// or the smis were not a valid slice of the base string,
|
|
// or the slice was too large to fit in the result.
|
|
// Something is wrong with the matches array!
|
|
Exceptions::ThrowArgumentError(matches_growable);
|
|
} else if (object.IsString()) {
|
|
const String& replacement = String::Cast(object);
|
|
intptr_t replacement_length = replacement.Length();
|
|
if (write_index + replacement_length > length) {
|
|
// Invalid input data, either in matches list or the total length.
|
|
Exceptions::ThrowArgumentError(matches_growable);
|
|
}
|
|
String::Copy(result, write_index, replacement, 0, replacement_length);
|
|
write_index += replacement_length;
|
|
}
|
|
}
|
|
if (write_index < length) {
|
|
Exceptions::ThrowArgumentError(matches_growable);
|
|
}
|
|
return result.raw();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(OneByteString_substringUnchecked, 3) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
ASSERT(receiver.IsOneByteString());
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
|
|
|
|
const intptr_t start = start_obj.Value();
|
|
const intptr_t end = end_obj.Value();
|
|
return OneByteString::New(receiver, start, end - start, Heap::kNew);
|
|
}
|
|
|
|
// This is high-performance code.
|
|
DEFINE_NATIVE_ENTRY(OneByteString_splitWithCharCode, 2) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
ASSERT(receiver.IsOneByteString());
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, smi_split_code, arguments->NativeArgAt(1));
|
|
const intptr_t len = receiver.Length();
|
|
const intptr_t split_code = smi_split_code.Value();
|
|
const GrowableObjectArray& result = GrowableObjectArray::Handle(
|
|
zone, GrowableObjectArray::New(16, Heap::kNew));
|
|
String& str = String::Handle(zone);
|
|
intptr_t start = 0;
|
|
intptr_t i = 0;
|
|
for (; i < len; i++) {
|
|
if (split_code == OneByteString::CharAt(receiver, i)) {
|
|
str = OneByteString::SubStringUnchecked(receiver, start, (i - start),
|
|
Heap::kNew);
|
|
result.Add(str);
|
|
start = i + 1;
|
|
}
|
|
}
|
|
str = OneByteString::SubStringUnchecked(receiver, start, (i - start),
|
|
Heap::kNew);
|
|
result.Add(str);
|
|
return result.raw();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(OneByteString_allocate, 1) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, length_obj, arguments->NativeArgAt(0));
|
|
return OneByteString::New(length_obj.Value(), Heap::kNew);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(OneByteString_allocateFromOneByteList, 3) {
|
|
Instance& list = Instance::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
|
|
|
|
intptr_t start = start_obj.Value();
|
|
intptr_t end = end_obj.Value();
|
|
if (start < 0) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, start_obj);
|
|
Exceptions::ThrowByType(Exceptions::kArgument, args);
|
|
}
|
|
intptr_t length = end - start;
|
|
if (length < 0) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, end_obj);
|
|
Exceptions::ThrowByType(Exceptions::kArgument, args);
|
|
}
|
|
ASSERT(length >= 0);
|
|
|
|
Heap::Space space = Heap::kNew;
|
|
if (list.IsTypedData()) {
|
|
const TypedData& array = TypedData::Cast(list);
|
|
if (end > array.LengthInBytes()) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, end_obj);
|
|
Exceptions::ThrowByType(Exceptions::kArgument, args);
|
|
}
|
|
return OneByteString::New(array, start, length, space);
|
|
} else if (list.IsExternalTypedData()) {
|
|
const ExternalTypedData& array = ExternalTypedData::Cast(list);
|
|
if (end > array.LengthInBytes()) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, end_obj);
|
|
Exceptions::ThrowByType(Exceptions::kArgument, args);
|
|
}
|
|
return OneByteString::New(array, start, length, space);
|
|
} else if (RawObject::IsTypedDataViewClassId(list.GetClassId())) {
|
|
const Instance& view = Instance::Cast(list);
|
|
if (end > Smi::Value(TypedDataView::Length(view))) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, end_obj);
|
|
Exceptions::ThrowByType(Exceptions::kArgument, args);
|
|
}
|
|
const Instance& data_obj = Instance::Handle(TypedDataView::Data(view));
|
|
intptr_t data_offset = Smi::Value(TypedDataView::OffsetInBytes(view));
|
|
if (data_obj.IsTypedData()) {
|
|
const TypedData& array = TypedData::Cast(data_obj);
|
|
return OneByteString::New(array, data_offset + start, length, space);
|
|
} else if (data_obj.IsExternalTypedData()) {
|
|
const ExternalTypedData& array = ExternalTypedData::Cast(data_obj);
|
|
return OneByteString::New(array, data_offset + start, length, space);
|
|
}
|
|
} else if (list.IsArray()) {
|
|
const Array& array = Array::Cast(list);
|
|
if (end > array.Length()) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, end_obj);
|
|
Exceptions::ThrowByType(Exceptions::kArgument, args);
|
|
}
|
|
String& string = String::Handle(OneByteString::New(length, space));
|
|
for (int i = 0; i < length; i++) {
|
|
intptr_t value =
|
|
Smi::Value(reinterpret_cast<RawSmi*>(array.At(start + i)));
|
|
OneByteString::SetCharAt(string, i, value);
|
|
}
|
|
return string.raw();
|
|
} else if (list.IsGrowableObjectArray()) {
|
|
const GrowableObjectArray& array = GrowableObjectArray::Cast(list);
|
|
if (end > array.Length()) {
|
|
const Array& args = Array::Handle(Array::New(1));
|
|
args.SetAt(0, end_obj);
|
|
Exceptions::ThrowByType(Exceptions::kArgument, args);
|
|
}
|
|
String& string = String::Handle(OneByteString::New(length, space));
|
|
for (int i = 0; i < length; i++) {
|
|
intptr_t value =
|
|
Smi::Value(reinterpret_cast<RawSmi*>(array.At(start + i)));
|
|
OneByteString::SetCharAt(string, i, value);
|
|
}
|
|
return string.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(OneByteString_setAt, 3) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, receiver, arguments->NativeArgAt(0));
|
|
ASSERT(receiver.IsOneByteString());
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, index_obj, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, code_point_obj, arguments->NativeArgAt(2));
|
|
ASSERT((0 <= code_point_obj.Value()) && (code_point_obj.Value() <= 0xFF));
|
|
OneByteString::SetCharAt(receiver, index_obj.Value(), code_point_obj.Value());
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(TwoByteString_allocateFromTwoByteList, 3) {
|
|
Instance& list = Instance::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, start_obj, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, end_obj, arguments->NativeArgAt(2));
|
|
|
|
intptr_t start = start_obj.Value();
|
|
intptr_t end = end_obj.Value();
|
|
if (start < 0) {
|
|
Exceptions::ThrowArgumentError(start_obj);
|
|
}
|
|
intptr_t length = end - start;
|
|
if (length < 0) {
|
|
Exceptions::ThrowArgumentError(end_obj);
|
|
}
|
|
|
|
Heap::Space space = Heap::kNew;
|
|
if (list.IsTypedData()) {
|
|
const TypedData& array = TypedData::Cast(list);
|
|
if (array.ElementType() != kUint16ArrayElement) {
|
|
Exceptions::ThrowArgumentError(list);
|
|
}
|
|
if (end > array.Length()) {
|
|
Exceptions::ThrowArgumentError(end_obj);
|
|
}
|
|
return TwoByteString::New(array, start * sizeof(uint16_t), length, space);
|
|
} else if (list.IsExternalTypedData()) {
|
|
const ExternalTypedData& array = ExternalTypedData::Cast(list);
|
|
if (array.ElementType() != kUint16ArrayElement) {
|
|
Exceptions::ThrowArgumentError(list);
|
|
}
|
|
if (end > array.Length()) {
|
|
Exceptions::ThrowArgumentError(end_obj);
|
|
}
|
|
return TwoByteString::New(array, start * sizeof(uint16_t), length, space);
|
|
} else if (RawObject::IsTypedDataViewClassId(list.GetClassId())) {
|
|
const intptr_t cid = list.GetClassId();
|
|
if (cid != kTypedDataUint16ArrayViewCid) {
|
|
Exceptions::ThrowArgumentError(list);
|
|
}
|
|
if (end > Smi::Value(TypedDataView::Length(list))) {
|
|
Exceptions::ThrowArgumentError(end_obj);
|
|
}
|
|
const Instance& data_obj =
|
|
Instance::Handle(zone, TypedDataView::Data(list));
|
|
intptr_t data_offset = Smi::Value(TypedDataView::OffsetInBytes(list));
|
|
if (data_obj.IsTypedData()) {
|
|
const TypedData& array = TypedData::Cast(data_obj);
|
|
return TwoByteString::New(array, data_offset + start * sizeof(uint16_t),
|
|
length, space);
|
|
} else if (data_obj.IsExternalTypedData()) {
|
|
const ExternalTypedData& array = ExternalTypedData::Cast(data_obj);
|
|
return TwoByteString::New(array, data_offset + start * sizeof(uint16_t),
|
|
length, space);
|
|
}
|
|
} else if (list.IsArray()) {
|
|
const Array& array = Array::Cast(list);
|
|
if (end > array.Length()) {
|
|
Exceptions::ThrowArgumentError(end_obj);
|
|
}
|
|
const String& string =
|
|
String::Handle(zone, TwoByteString::New(length, space));
|
|
for (int i = 0; i < length; i++) {
|
|
intptr_t value =
|
|
Smi::Value(reinterpret_cast<RawSmi*>(array.At(start + i)));
|
|
TwoByteString::SetCharAt(string, i, value);
|
|
}
|
|
return string.raw();
|
|
} else if (list.IsGrowableObjectArray()) {
|
|
const GrowableObjectArray& array = GrowableObjectArray::Cast(list);
|
|
if (end > array.Length()) {
|
|
Exceptions::ThrowArgumentError(end_obj);
|
|
}
|
|
const String& string =
|
|
String::Handle(zone, TwoByteString::New(length, space));
|
|
for (int i = 0; i < length; i++) {
|
|
intptr_t value =
|
|
Smi::Value(reinterpret_cast<RawSmi*>(array.At(start + i)));
|
|
TwoByteString::SetCharAt(string, i, value);
|
|
}
|
|
return string.raw();
|
|
}
|
|
UNREACHABLE();
|
|
return Object::null();
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(String_getHashCode, 1) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
intptr_t hash_val = receiver.Hash();
|
|
ASSERT(hash_val > 0);
|
|
ASSERT(Smi::IsValid(hash_val));
|
|
return Smi::New(hash_val);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(String_getLength, 1) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
return Smi::New(receiver.Length());
|
|
}
|
|
|
|
static uint16_t StringValueAt(const String& str, const Integer& index) {
|
|
if (index.IsSmi()) {
|
|
const intptr_t index_value = Smi::Cast(index).Value();
|
|
if ((0 <= index_value) && (index_value < str.Length())) {
|
|
return str.CharAt(index_value);
|
|
}
|
|
}
|
|
|
|
// An index larger than Smi is always illegal.
|
|
Exceptions::ThrowRangeError("index", index, 0, str.Length() - 1);
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(String_charAt, 2) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Integer, index, arguments->NativeArgAt(1));
|
|
uint16_t value = StringValueAt(receiver, index);
|
|
return Symbols::FromCharCode(thread, static_cast<int32_t>(value));
|
|
}
|
|
|
|
// Returns the 16-bit UTF-16 code unit at the given index.
|
|
DEFINE_NATIVE_ENTRY(String_codeUnitAt, 2) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Integer, index, arguments->NativeArgAt(1));
|
|
uint16_t value = StringValueAt(receiver, index);
|
|
return Smi::New(static_cast<intptr_t>(value));
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(String_concat, 2) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(String, b, arguments->NativeArgAt(1));
|
|
return String::Concat(receiver, b);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(String_toLowerCase, 1) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
ASSERT(!receiver.IsNull());
|
|
return String::ToLowerCase(receiver);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(String_toUpperCase, 1) {
|
|
const String& receiver =
|
|
String::CheckedHandle(zone, arguments->NativeArgAt(0));
|
|
ASSERT(!receiver.IsNull());
|
|
return String::ToUpperCase(receiver);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(String_concatRange, 3) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Instance, argument, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, start, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, end, arguments->NativeArgAt(2));
|
|
const intptr_t start_ix = start.Value();
|
|
const intptr_t end_ix = end.Value();
|
|
if (start_ix < 0) {
|
|
Exceptions::ThrowArgumentError(start);
|
|
}
|
|
Array& strings = Array::Handle();
|
|
intptr_t length = -1;
|
|
if (argument.IsArray()) {
|
|
strings ^= argument.raw();
|
|
length = strings.Length();
|
|
} else if (argument.IsGrowableObjectArray()) {
|
|
const GrowableObjectArray& g_array = GrowableObjectArray::Cast(argument);
|
|
strings = g_array.data();
|
|
length = g_array.Length();
|
|
} else {
|
|
Exceptions::ThrowArgumentError(argument);
|
|
}
|
|
if (end_ix > length) {
|
|
Exceptions::ThrowArgumentError(end);
|
|
}
|
|
#if defined(DEBUG)
|
|
// Check that the array contains strings.
|
|
Instance& elem = Instance::Handle();
|
|
for (intptr_t i = start_ix; i < end_ix; i++) {
|
|
elem ^= strings.At(i);
|
|
ASSERT(elem.IsString());
|
|
}
|
|
#endif
|
|
return String::ConcatAllRange(strings, start_ix, end_ix, Heap::kNew);
|
|
}
|
|
|
|
DEFINE_NATIVE_ENTRY(StringBuffer_createStringFromUint16Array, 3) {
|
|
GET_NON_NULL_NATIVE_ARGUMENT(TypedData, codeUnits, arguments->NativeArgAt(0));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Smi, length, arguments->NativeArgAt(1));
|
|
GET_NON_NULL_NATIVE_ARGUMENT(Bool, isLatin1, arguments->NativeArgAt(2));
|
|
intptr_t array_length = codeUnits.Length();
|
|
intptr_t length_value = length.Value();
|
|
if (length_value < 0 || length_value > array_length) {
|
|
Exceptions::ThrowRangeError("length", length, 0, array_length);
|
|
}
|
|
const String& result =
|
|
isLatin1.value()
|
|
? String::Handle(OneByteString::New(length_value, Heap::kNew))
|
|
: String::Handle(TwoByteString::New(length_value, Heap::kNew));
|
|
NoSafepointScope no_safepoint;
|
|
|
|
uint16_t* data_position = reinterpret_cast<uint16_t*>(codeUnits.DataAddr(0));
|
|
String::Copy(result, 0, data_position, length_value);
|
|
return result.raw();
|
|
}
|
|
|
|
} // namespace dart
|