532 lines
18 KiB
TypeScript
Raw Normal View History

2019-06-05 23:15:39 +02:00
/// <reference path="./rt/index.d.ts" />
import { BLOCK, BLOCK_OVERHEAD, BLOCK_MAXSIZE } from "./rt/common";
import { compareImpl, strtol, strtod, isWhiteSpaceOrLineTerminator } from "./util/string";
2019-06-05 23:15:39 +02:00
import { E_INVALIDLENGTH } from "./util/error";
import { ArrayBufferView } from "./arraybuffer";
import { idof } from "./builtins";
@sealed export abstract class String {
@lazy static readonly MAX_LENGTH: i32 = BLOCK_MAXSIZE >>> alignof<u16>();
static fromCharCode(unit: i32, surr: i32 = -1): string {
var out: usize;
if (~surr) {
out = __alloc(4, idof<string>());
store<u16>(out, <u16>unit);
store<u16>(out, <u16>surr, 2);
} else {
out = __alloc(2, idof<string>());
store<u16>(out, <u16>unit);
}
return changetype<string>(out); // retains
2018-07-13 16:40:37 +03:00
}
2019-06-05 23:15:39 +02:00
static fromCodePoint(code: i32): string {
assert(<u32>code <= 0x10FFFF);
var sur = code > 0xFFFF;
2019-06-05 23:15:39 +02:00
var out = __alloc((i32(sur) + 1) << 1, idof<string>());
if (!sur) {
2019-06-05 23:15:39 +02:00
store<u16>(out, <u16>code);
} else {
code -= 0x10000;
let hi: u32 = (code >>> 10) + 0xD800;
let lo: u32 = (code & 0x3FF) + 0xDC00;
2019-06-05 23:15:39 +02:00
store<u32>(out, (hi << 16) | lo);
}
2019-06-05 23:15:39 +02:00
return changetype<string>(out); // retains
}
2019-06-05 23:15:39 +02:00
get length(): i32 {
return changetype<BLOCK>(changetype<usize>(this) - BLOCK_OVERHEAD).rtSize >> 1;
}
2018-01-28 06:18:27 +01:00
2019-06-05 23:15:39 +02:00
@operator("[]") charAt(pos: i32): String {
assert(this !== null);
2018-08-02 18:23:02 +02:00
if (<u32>pos >= <u32>this.length) return changetype<String>("");
2019-06-05 23:15:39 +02:00
var out = __alloc(2, idof<String>());
store<u16>(out, load<u16>(changetype<usize>(this) + (<usize>pos << 1)));
return changetype<String>(out); // retains
}
charCodeAt(pos: i32): i32 {
if (<u32>pos >= <u32>this.length) return -1; // (NaN)
2019-06-05 23:15:39 +02:00
return load<u16>(changetype<usize>(this) + (<usize>pos << 1));
}
codePointAt(pos: i32): i32 {
if (<u32>pos >= <u32>this.length) return -1; // (undefined)
2019-06-05 23:15:39 +02:00
var first = <i32>load<u16>(changetype<usize>(this) + (<usize>pos << 1));
if (first < 0xD800 || first > 0xDBFF || pos + 1 == this.length) return first;
var second = <i32>load<u16>(changetype<usize>(this) + ((<usize>pos + 1) << 1));
if (second < 0xDC00 || second > 0xDFFF) return first;
return ((first - 0xD800) << 10) + (second - 0xDC00) + 0x10000;
}
2019-06-05 23:15:39 +02:00
@operator("+") private static __concat(left: String, right: String): String {
return select<String>(left, changetype<String>("null"), left !== null).concat(right);
2018-01-28 06:18:27 +01:00
}
concat(other: String): String {
if (other === null) other = changetype<String>("null");
2019-06-05 23:15:39 +02:00
var thisSize: isize = this.length << 1;
var otherSize: isize = other.length << 1;
var outSize: usize = thisSize + otherSize;
if (outSize == 0) return changetype<String>("");
var out = changetype<String>(__alloc(outSize, idof<String>())); // retains
memory.copy(changetype<usize>(out), changetype<usize>(this), thisSize);
memory.copy(changetype<usize>(out) + thisSize, changetype<usize>(other), otherSize);
return out;
}
2019-06-05 23:15:39 +02:00
endsWith(search: String, end: i32 = String.MAX_LENGTH): bool {
assert(this !== null);
2019-06-05 23:15:39 +02:00
if (search === null) return false;
end = min(max(end, 0), this.length);
var searchLength = <isize>search.length;
var searchStart = <isize>end - searchLength;
if (searchStart < 0) return false;
// @ts-ignore: string <-> String
return !compareImpl(this, searchStart, search, 0, searchLength);
}
2019-06-05 23:15:39 +02:00
@operator("==") private static __eq(left: String | null, right: String | null): bool {
if (left === right) return true;
if (left === null || right === null) return false;
2018-01-28 06:18:27 +01:00
var leftLength = left.length;
if (leftLength != right.length) return false;
2019-06-05 23:15:39 +02:00
// @ts-ignore: string <-> String
return !compareImpl(left, 0, right, 0, leftLength);
}
@operator.prefix("!")
2019-06-05 23:15:39 +02:00
private static __not(str: String | null): bool {
return str === null || !str.length;
}
@operator("!=")
2019-06-05 23:15:39 +02:00
private static __ne(left: String | null, right: String | null): bool {
return !this.__eq(left, right);
}
2019-06-05 23:15:39 +02:00
@operator(">") private static __gt(left: String | null, right: String | null): bool {
2018-04-07 19:59:26 +03:00
if (left === right || left === null || right === null) return false;
var leftLength = left.length;
var rightLength = right.length;
if (!leftLength) return false;
if (!rightLength) return true;
2019-06-05 23:15:39 +02:00
// @ts-ignore: string <-> String
return compareImpl(left, 0, right, 0, min(leftLength, rightLength)) > 0;
}
2019-06-05 23:15:39 +02:00
@operator(">=") private static __gte(left: String, right: String): bool {
return !this.__lt(left, right);
}
2019-06-05 23:15:39 +02:00
@operator("<") private static __lt(left: String, right: String): bool {
2018-04-07 19:59:26 +03:00
if (left === right || left === null || right === null) return false;
var leftLength = left.length;
var rightLength = right.length;
if (!rightLength) return false;
if (!leftLength) return true;
2019-06-05 23:15:39 +02:00
// @ts-ignore: string <-> String
return compareImpl(left, 0, right, 0, min(leftLength, rightLength)) < 0;
}
2019-06-05 23:15:39 +02:00
@operator("<=") private static __lte(left: String, right: String): bool {
return !this.__gt(left, right);
}
2019-06-05 23:15:39 +02:00
includes(search: String, start: i32 = 0): bool {
return this.indexOf(search, start) != -1;
}
2019-06-05 23:15:39 +02:00
indexOf(search: String, start: i32 = 0): i32 {
var searchLen = <isize>search.length;
if (!searchLen) return 0;
2019-06-05 23:15:39 +02:00
var len = <isize>this.length;
if (!len) return -1;
2019-06-05 23:15:39 +02:00
var searchStart = min(max(<isize>start, 0), len);
for (len -= searchLen; searchStart <= len; ++searchStart) {
// @ts-ignore: string <-> String
if (!compareImpl(this, searchStart, search, 0, searchLen)) return <i32>searchStart;
}
return -1;
}
2019-06-05 23:15:39 +02:00
lastIndexOf(search: String, start: i32 = i32.MAX_VALUE): i32 {
var searchLen = <isize>search.length;
if (!searchLen) return this.length;
var len = this.length;
if (!len) return -1;
2019-06-05 23:15:39 +02:00
var searchStart = min(max(<isize>start, 0), <isize>len - searchLen);
for (; searchStart >= 0; --searchStart) {
// @ts-ignore: string <-> String
if (!compareImpl(this, searchStart, search, 0, searchLen)) return <i32>searchStart;
2018-02-25 23:21:32 +01:00
}
return -1;
}
2019-06-05 23:15:39 +02:00
startsWith(search: String, start: i32 = 0): bool {
assert(this !== null);
2019-06-05 23:15:39 +02:00
if (search === null) search = changetype<String>("null");
var len = <isize>this.length;
var searchStart = min(max(<isize>start, 0), len);
var searchLength = <isize>search.length;
if (searchLength + searchStart > len) return false;
// @ts-ignore: string <-> String
return !compareImpl(this, searchStart, search, 0, searchLength);
}
2019-06-05 23:15:39 +02:00
substr(start: i32, length: i32 = i32.MAX_VALUE): String { // legacy
assert(this !== null);
var intStart: isize = start;
var end: isize = length;
var size: isize = this.length;
2018-08-02 18:23:02 +02:00
if (intStart < 0) intStart = max(size + intStart, 0);
var resultLength = min(max(end, 0), size - intStart);
if (resultLength <= 0) return changetype<String>("");
2019-06-05 23:15:39 +02:00
var out = __alloc(resultLength << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + intStart, resultLength);
return changetype<String>(out); // retains
}
substring(start: i32, end: i32 = i32.MAX_VALUE): String {
assert(this !== null);
2019-06-05 23:15:39 +02:00
var len: isize = this.length;
var finalStart = min<isize>(max(start, 0), len);
var finalEnd = min<isize>(max(end, 0), len);
var fromPos = min<isize>(finalStart, finalEnd) << 1;
var toPos = max<isize>(finalStart, finalEnd) << 1;
len = toPos - fromPos;
2018-08-02 18:23:02 +02:00
if (!len) return changetype<String>("");
2019-06-05 23:15:39 +02:00
if (!fromPos && toPos == this.length << 1) return this;
var out = __alloc(len, idof<String>());
memory.copy(out, changetype<usize>(this) + fromPos, len);
return changetype<String>(out); // retains
}
trim(): String {
assert(this !== null);
2019-06-05 23:15:39 +02:00
var length = this.length;
var size: usize = length << 1;
2018-02-25 23:21:32 +01:00
while (
2019-06-05 23:15:39 +02:00
size &&
2018-02-25 23:21:32 +01:00
isWhiteSpaceOrLineTerminator(
2019-06-05 23:15:39 +02:00
load<u16>(changetype<usize>(this) + size)
2018-02-25 23:21:32 +01:00
)
) {
2019-06-05 23:15:39 +02:00
size -= 2;
2018-02-25 23:21:32 +01:00
}
2019-06-05 23:15:39 +02:00
var offset: usize = 0;
2018-02-25 23:21:32 +01:00
while (
2019-06-05 23:15:39 +02:00
offset < size &&
2018-02-25 23:21:32 +01:00
isWhiteSpaceOrLineTerminator(
2019-06-05 23:15:39 +02:00
load<u16>(changetype<usize>(this) + offset)
2018-02-25 23:21:32 +01:00
)
) {
2019-06-05 23:15:39 +02:00
offset += 2; size -= 2;
2018-02-25 23:21:32 +01:00
}
2019-06-05 23:15:39 +02:00
if (!size) return changetype<String>("");
if (!start && size == length << 1) return this;
var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this) + offset, size);
return changetype<String>(out); // retains
}
2018-09-30 23:21:34 +03:00
@inline
trimLeft(): String {
2018-09-30 23:21:34 +03:00
return this.trimStart();
}
@inline
trimRight(): String {
return this.trimEnd();
}
trimStart(): String {
assert(this !== null);
2019-06-05 23:15:39 +02:00
var size = <usize>this.length << 1;
var offset: usize = 0;
2018-02-25 23:21:32 +01:00
while (
2019-06-05 23:15:39 +02:00
offset < size &&
2018-02-25 23:21:32 +01:00
isWhiteSpaceOrLineTerminator(
2019-06-05 23:15:39 +02:00
load<u16>(changetype<usize>(this) + offset)
2018-02-25 23:21:32 +01:00
)
) {
2019-06-05 23:15:39 +02:00
offset += 2;
2018-02-25 23:21:32 +01:00
}
2019-06-05 23:15:39 +02:00
if (!offset) return this;
size -= offset;
if (!size) return changetype<String>("");
var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this) + offset, size);
return changetype<String>(out); // retains
}
2018-09-30 23:21:34 +03:00
trimEnd(): String {
assert(this !== null);
2019-06-05 23:15:39 +02:00
var originalSize = <usize>this.length << 1;
var size = originalSize;
2018-02-25 23:21:32 +01:00
while (
2019-06-05 23:15:39 +02:00
size &&
2018-02-25 23:21:32 +01:00
isWhiteSpaceOrLineTerminator(
2019-06-05 23:15:39 +02:00
load<u16>(changetype<usize>(this) + size)
2018-02-25 23:21:32 +01:00
)
) {
2019-06-05 23:15:39 +02:00
size -= 2;
2018-02-25 23:21:32 +01:00
}
2019-06-05 23:15:39 +02:00
if (!size) return changetype<String>("");
if (size == originalSize) return this;
var out = __alloc(size, idof<String>());
memory.copy(out, changetype<usize>(this), size);
return changetype<String>(out); // retains
}
2019-06-05 23:15:39 +02:00
padStart(length: i32, pad: string = " "): String {
assert(this !== null);
2019-06-05 23:15:39 +02:00
var thisSize = <usize>this.length << 1;
var targetSize = <usize>length << 1;
var padSize = <usize>pad.length << 1;
if (targetSize < thisSize || !padSize) return this;
var prependSize = targetSize - thisSize;
var out = __alloc(targetSize, idof<String>());
if (prependSize > padSize) {
let repeatCount = (prependSize - 2) / padSize;
let restBase = repeatCount * padSize;
let restSize = prependSize - restBase;
memory.repeat(out, changetype<usize>(pad), padSize, repeatCount);
memory.copy(out + restBase, changetype<usize>(pad), restSize);
} else {
2019-06-05 23:15:39 +02:00
memory.copy(out, changetype<usize>(pad), prependSize);
2018-02-25 23:21:32 +01:00
}
2019-06-05 23:15:39 +02:00
memory.copy(out + prependSize, changetype<usize>(this), thisSize);
return changetype<String>(out); // retains
}
2019-06-05 23:15:39 +02:00
padEnd(length: i32, pad: string = " "): String {
assert(this !== null);
2019-06-05 23:15:39 +02:00
var thisSize = <usize>this.length << 1;
var targetSize = <usize>length << 1;
var padSize = <usize>pad.length << 1;
if (targetSize < thisSize || !padSize) return this;
var appendSize = targetSize - thisSize;
var out = __alloc(targetSize, idof<String>());
memory.copy(out, changetype<usize>(this), thisSize);
if (appendSize > padSize) {
let repeatCount = (appendSize - 2) / padSize;
let restBase = repeatCount * padSize;
let restSize = appendSize - restBase;
memory.repeat(out + thisSize, changetype<usize>(pad), padSize, repeatCount);
memory.copy(out + thisSize + restBase, changetype<usize>(pad), restSize);
} else {
2019-06-05 23:15:39 +02:00
memory.copy(out + thisSize, changetype<usize>(pad), appendSize);
2018-02-25 23:21:32 +01:00
}
2019-06-05 23:15:39 +02:00
return changetype<String>(out); // retains
}
2018-04-07 19:59:26 +03:00
repeat(count: i32 = 0): String {
assert(this !== null);
var length = this.length;
// Most browsers can't handle strings 1 << 28 chars or longer
2019-06-05 23:15:39 +02:00
if (count < 0 || <u64>length * count > (1 << 28)) {
throw new RangeError(E_INVALIDLENGTH);
2018-04-07 19:59:26 +03:00
}
2018-10-18 20:05:35 +03:00
if (count == 0 || !length) return changetype<String>("");
if (count == 1) return this;
2019-06-05 23:15:39 +02:00
var out = __alloc((length * count) << 1, idof<String>());
memory.repeat(out, changetype<usize>(this), <usize>length << 1, count);
return changetype<String>(out); // retains
2018-04-07 19:59:26 +03:00
}
2019-06-05 23:15:39 +02:00
slice(start: i32, end: i32 = i32.MAX_VALUE): String {
var len = this.length;
2019-06-05 23:15:39 +02:00
start = start < 0 ? max(start + len, 0) : min(start, len);
end = end < 0 ? max(end + len, 0) : min(end, len);
len = end - start;
2019-01-10 19:10:23 +08:00
if (len <= 0) return changetype<String>("");
2019-06-05 23:15:39 +02:00
var out = __alloc(len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
return changetype<String>(out); // retains
2019-01-10 19:10:23 +08:00
}
2019-06-05 23:15:39 +02:00
split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] {
2018-10-18 20:05:35 +03:00
assert(this !== null);
2019-06-05 23:15:39 +02:00
if (!limit) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
2018-10-18 20:05:35 +03:00
if (separator === null) return <String[]>[this];
var length: isize = this.length;
var sepLen: isize = separator.length;
if (limit < 0) limit = i32.MAX_VALUE;
if (!sepLen) {
2019-06-05 23:15:39 +02:00
if (!length) return changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
2018-10-18 20:05:35 +03:00
// split by chars
length = min<isize>(length, <isize>limit);
2019-06-05 23:15:39 +02:00
let result = __allocArray(length, alignof<String>(), idof<Array<String>>());
let resultStart = changetype<ArrayBufferView>(result).dataStart;
2018-10-18 20:05:35 +03:00
for (let i: isize = 0; i < length; ++i) {
2019-06-05 23:15:39 +02:00
let charStr = __alloc(2, idof<String>());
store<u16>(charStr, load<u16>(changetype<usize>(this) + (<usize>i << 1)));
store<usize>(resultStart + (<usize>i << alignof<usize>()), charStr); // result[i] = charStr
if (isManaged<String>()) __retain(charStr);
2018-10-18 20:05:35 +03:00
}
2019-06-05 23:15:39 +02:00
return changetype<Array<String>>(result); // retains
2018-10-18 20:05:35 +03:00
} else if (!length) {
2019-06-05 23:15:39 +02:00
let result = __allocArray(1, alignof<String>(), idof<Array<String>>());
store<usize>(changetype<ArrayBufferView>(result).dataStart, changetype<usize>("")); // static ""
return changetype<Array<String>>(result); // retains
2018-10-18 20:05:35 +03:00
}
2019-06-05 23:15:39 +02:00
var result = changetype<Array<String>>(__allocArray(0, alignof<String>(), idof<Array<String>>())); // retains
2018-10-18 20:05:35 +03:00
var end = 0, start = 0, i = 0;
while ((end = this.indexOf(separator, start)) != -1) {
let len = end - start;
if (len > 0) {
2019-06-05 23:15:39 +02:00
let out = __alloc(<usize>len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(changetype<String>(out));
2018-10-18 20:05:35 +03:00
} else {
result.push(changetype<String>(""));
}
2019-06-05 23:15:39 +02:00
if (++i == limit) return changetype<Array<String>>(result); // retains
2018-10-18 20:05:35 +03:00
start = end + sepLen;
}
2019-06-05 23:15:39 +02:00
if (!start) { // also means: loop above didn't do anything
result.push(this);
return changetype<Array<String>>(result); // retains
}
2018-10-18 20:05:35 +03:00
var len = length - start;
if (len > 0) {
2019-06-05 23:15:39 +02:00
let out = __alloc(<usize>len << 1, idof<String>());
memory.copy(out, changetype<usize>(this) + (<usize>start << 1), <usize>len << 1);
result.push(changetype<String>(out)); // retains
2018-10-18 20:05:35 +03:00
} else {
2019-06-05 23:15:39 +02:00
result.push(changetype<String>("")); // static ""
2018-10-18 20:05:35 +03:00
}
2019-06-05 23:15:39 +02:00
return changetype<Array<String>>(result); // retains
// releases result
2018-10-18 20:05:35 +03:00
}
2018-04-07 19:59:26 +03:00
toString(): String {
return this;
}
get lengthUTF8(): i32 {
var len = 1; // null terminated
var pos: usize = 0;
var end = <usize>this.length;
while (pos < end) {
2019-06-05 23:15:39 +02:00
let c = <u32>load<u16>(changetype<usize>(this) + (pos << 1));
if (c < 128) {
len += 1; ++pos;
} else if (c < 2048) {
len += 2; ++pos;
} else {
if (
(c & 0xFC00) == 0xD800 && pos + 1 < end &&
2019-06-05 23:15:39 +02:00
(<u32>load<u16>(changetype<usize>(this) + ((pos + 1) << 1)) & 0xFC00) == 0xDC00
) {
len += 4; pos += 2;
} else {
len += 3; ++pos;
}
}
}
return len;
}
static fromUTF8(ptr: usize, len: usize): String {
if (len < 1) return changetype<String>("");
var ptrPos = <usize>0;
2019-06-05 23:15:39 +02:00
var buf = __alloc(<usize>len << 1, 0);
var bufPos = <usize>0;
while (ptrPos < len) {
let cp = <u32>load<u8>(ptr + ptrPos++);
if (cp < 128) {
store<u16>(buf + bufPos, cp);
bufPos += 2;
} else if (cp > 191 && cp < 224) {
assert(ptrPos + 1 <= len);
store<u16>(buf + bufPos, (cp & 31) << 6 | load<u8>(ptr + ptrPos++) & 63);
bufPos += 2;
} else if (cp > 239 && cp < 365) {
assert(ptrPos + 3 <= len);
cp = (
(cp & 7) << 18 |
(load<u8>(ptr + ptrPos++) & 63) << 12 |
(load<u8>(ptr + ptrPos++) & 63) << 6 |
load<u8>(ptr + ptrPos++) & 63
) - 0x10000;
store<u16>(buf + bufPos, 0xD800 + (cp >> 10));
bufPos += 2;
store<u16>(buf + bufPos, 0xDC00 + (cp & 1023));
bufPos += 2;
} else {
assert(ptrPos + 2 <= len);
store<u16>(buf + bufPos,
(cp & 15) << 12 |
(load<u8>(ptr + ptrPos++) & 63) << 6 |
load<u8>(ptr + ptrPos++) & 63
);
bufPos += 2;
}
}
assert(ptrPos == len);
2019-06-05 23:15:39 +02:00
var out = __alloc(bufPos, idof<String>());
memory.copy(out, buf, bufPos);
__free(buf);
return changetype<String>(out); // retains
}
toUTF8(): usize {
2019-06-05 23:15:39 +02:00
var buf = __alloc(<usize>this.lengthUTF8, 0);
var pos: usize = 0;
var end = <usize>this.length;
var off: usize = 0;
while (pos < end) {
2019-06-05 23:15:39 +02:00
let c1 = <u32>load<u16>(changetype<usize>(this) + (pos << 1));
if (c1 < 128) {
store<u8>(buf + off, c1);
++off; ++pos;
} else if (c1 < 2048) {
let ptr = buf + off;
store<u8>(ptr, c1 >> 6 | 192);
store<u8>(ptr, c1 & 63 | 128, 1);
off += 2; ++pos;
} else {
let ptr = buf + off;
if ((c1 & 0xFC00) == 0xD800 && pos + 1 < end) {
2019-06-05 23:15:39 +02:00
let c2 = <u32>load<u16>(changetype<usize>(this) + ((pos + 1) << 1));
if ((c2 & 0xFC00) == 0xDC00) {
c1 = 0x10000 + ((c1 & 0x03FF) << 10) + (c2 & 0x03FF);
store<u8>(ptr, c1 >> 18 | 240);
store<u8>(ptr, c1 >> 12 & 63 | 128, 1);
store<u8>(ptr, c1 >> 6 & 63 | 128, 2);
store<u8>(ptr, c1 & 63 | 128, 3);
off += 4; pos += 2;
continue;
}
}
store<u8>(ptr, c1 >> 12 | 224);
store<u8>(ptr, c1 >> 6 & 63 | 128, 1);
store<u8>(ptr, c1 & 63 | 128, 2);
off += 3; ++pos;
}
}
store<u8>(buf + off, 0);
return buf;
}
}
2019-06-05 23:15:39 +02:00
// @ts-ignore: nolib
export type string = String;
export function parseInt(str: string, radix: i32 = 0): f64 {
return strtol<f64>(str, radix);
}
export function parseFloat(str: string): f64 {
return strtod(str);
}