blob: 754e0f6011cfb557f1f40237c1a8c95fb78a348a [file] [log] [blame]
// Copyright (c) 2023, 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.
import 'dart:_internal' show EfficientLengthIterable, patch, unsafeCast;
import 'dart:_js_helper' as js;
import 'dart:_js_types';
import 'dart:_string';
import 'dart:_wasm';
import 'dart:js_interop';
import 'dart:typed_data';
@patch
class String {
@patch
factory String.fromCharCodes(
Iterable<int> charCodes, [
int start = 0,
int? end,
]) {
RangeError.checkNotNegative(start, "start");
if (end != null) {
if (end < start) {
throw RangeError.range(end, start, null, "end");
}
if (end == start) return "";
}
// Skip until `start`.
final it = charCodes.iterator;
for (int i = 0; i < start; i++) {
it.moveNext();
}
// The part of the iterable converted to string is collected in a JS typed
// array, to be able to effciently get subarrays, to pass to
// `String.fromCharCode.apply`.
final charCodesLength = (end ?? charCodes.length) - start;
if (charCodesLength <= 0) return "";
final typedArrayLength = charCodesLength * 2;
final list = JSUint32ArrayImpl(typedArrayLength);
int index = 0; // index in `list`.
end ??= start + charCodesLength;
for (int i = start; i < end; i++) {
if (!it.moveNext()) {
break;
}
final charCode = it.current;
if (charCode >= 0 && charCode <= 0xffff) {
list[index++] = charCode;
} else if (charCode >= 0 && charCode <= 0x10ffff) {
list[index++] = 0xd800 + ((((charCode - 0x10000) >> 10) & 0x3ff));
list[index++] = 0xdc00 + (charCode & 0x3ff);
} else {
throw RangeError.range(charCode, 0, 0x10ffff);
}
}
// Create JS string from `list`.
const kMaxApply = 500;
if (index <= kMaxApply) {
return _fromCharCodeApplySubarray(list, 0, index);
}
String result = '';
for (int i = 0; i < index; i += kMaxApply) {
final chunkEnd = (i + kMaxApply < index) ? i + kMaxApply : index;
result += _fromCharCodeApplySubarray(list, i, chunkEnd);
}
return result;
}
@patch
factory String.fromCharCode(int charCode) => _fromCharCode(charCode);
static String _fromOneByteCharCode(int charCode) => JSStringImpl(
js.JS<WasmExternRef?>('c => String.fromCharCode(c)', charCode.toDouble()),
);
static String _fromTwoByteCharCode(int low, int high) => JSStringImpl(
js.JS<WasmExternRef?>(
'(l, h) => String.fromCharCode(h, l)',
low.toDouble(),
high.toDouble(),
),
);
static String _fromCharCode(int charCode) {
if (0 <= charCode) {
if (charCode <= 0xffff) {
return _fromOneByteCharCode(charCode);
}
if (charCode <= 0x10ffff) {
var bits = charCode - 0x10000;
var low = 0xDC00 | (bits & 0x3ff);
var high = 0xD800 | (bits >> 10);
return _fromTwoByteCharCode(low, high);
}
}
throw RangeError.range(charCode, 0, 0x10ffff);
}
static String _fromCharCodeApplySubarray(
JSUint32ArrayImpl charCodes,
int index,
int end,
) {
return JSStringImpl(
js.JS<WasmExternRef?>(
'(c, i, e) => String.fromCharCode.apply(null, new Uint32Array(c.buffer, c.byteOffset + i, e))',
charCodes.toExternRef,
WasmI32.fromInt(index * 4),
WasmI32.fromInt(end - index),
),
);
}
}
extension _StringExt on String {
int firstNonWhitespace() =>
unsafeCast<JSStringImpl>(this).firstNonWhitespace();
int lastNonWhitespace() => unsafeCast<JSStringImpl>(this).lastNonWhitespace();
}