See More

/// import { OBJECT, BLOCK_MAXSIZE, TOTAL_OVERHEAD } from "./rt/common"; import { compareImpl, strtol, strtod, isSpace, isAscii, isFinalSigma, toLower8, toUpper8 } from "./util/string"; import { SPECIALS_UPPER, casemap, bsearch } from "./util/casemap"; import { E_INDEXOUTOFRANGE, E_INVALIDLENGTH, E_UNPAIRED_SURROGATE } from "./util/error"; import { idof } from "./builtins"; import { Array } from "./array"; @final export abstract class String { @lazy static readonly MAX_LENGTH: i32 = (BLOCK_MAXSIZE >>> alignof()); static fromCharCode(unit: i32, surr: i32 = -1): String { let hasSur = surr > 0; let out = changetype(__new(2 << i32(hasSur), idof())); store(changetype(out), unit); if (hasSur) store(changetype(out), surr, 2); return out; } static fromCharCodes(units: Array): String { let length = units.length; let out = changetype(__new(length << 1, idof())); let ptr = units.dataStart; for (let i = 0; i < length; ++i) { store(changetype(out) + (i << 1), load(ptr + (i << 2))); } return out; } static fromCodePoint(code: i32): String { let hasSur = code > 0xFFFF; let out = changetype(__new(2 << i32(hasSur), idof())); if (!hasSur) { store(changetype(out), code); } else { // Checks valid code point range assert(code <= 0x10FFFF); code -= 0x10000; let hi = (code & 0x03FF) | 0xDC00; let lo = code >>> 10 | 0xD800; store(changetype(out), lo | hi << 16); } return out; } @builtin static raw(parts: TemplateStringsArray, ...args: unknown[]): string { return unreachable(); } get length(): i32 { return changetype(changetype(this) - TOTAL_OVERHEAD).rtSize >> 1; } at(pos: i32): String { let len = this.length; pos += select(0, len, pos >= 0); if (pos >= len) throw new RangeError(E_INDEXOUTOFRANGE); let out = __new(2, idof()); store(out, load(changetype(this) + (pos << 1))); return changetype(out); // retains } @operator("[]") charAt(pos: i32): String { if (pos >= this.length) return changetype(""); let out = changetype(__new(2, idof())); store(changetype(out), load(changetype(this) + (pos << 1))); return out; } charCodeAt(pos: i32): i32 { if (pos >= this.length) return -1; // (NaN) return load(changetype(this) + (pos << 1)); } codePointAt(pos: i32): i32 { let len = this.length; if (pos >= len) return -1; // (undefined) let first = load(changetype(this) + (pos << 1)); if ((first & 0xFC00) != 0xD800 || pos + 1 == len) return first; let second = load(changetype(this) + (pos << 1), 2); if ((second & 0xFC00) != 0xDC00) return first; return (first - 0xD800 << 10) + (second - 0xDC00) + 0x10000; } @operator("+") private static __concat(left: String, right: String): String { return left.concat(right); } concat(other: String): String { let thisSize: isize = this.length << 1; let otherSize: isize = other.length << 1; let outSize: usize = thisSize + otherSize; if (outSize == 0) return changetype(""); let out = changetype(__new(outSize, idof())); memory.copy(changetype(out), changetype(this), thisSize); memory.copy(changetype(out) + thisSize, changetype(other), otherSize); return out; } endsWith(search: String, end: i32 = String.MAX_LENGTH): bool { end = min(max(end, 0), this.length); let searchLength = search.length; let searchStart = end - searchLength; if (searchStart < 0) return false; // @ts-ignore: string <-> String return !compareImpl(this, searchStart, search, 0, searchLength); } @operator("==") private static __eq(left: String | null, right: String | null): bool { if (changetype(left) == changetype(right)) return true; if (changetype(left) == 0 || changetype(right) == 0) return false; let leftLength = changetype(left).length; if (leftLength != changetype(right).length) return false; // @ts-ignore: string <-> String return !compareImpl(left, 0, right, 0, leftLength); } @operator.prefix("!") private static __not(str: String | null): bool { return changetype(str) == 0 || !changetype(str).length; } @operator("!=") private static __ne(left: String | null, right: String | null): bool { return !this.__eq(left, right); } @operator(">") private static __gt(left: String, right: String): bool { if (changetype(left) == changetype(right)) return false; let leftLength = left.length; if (!leftLength) return false; let rightLength = right.length; if (!rightLength) return true; // @ts-ignore: string <-> String let res = compareImpl(left, 0, right, 0, min(leftLength, rightLength)); return res ? res > 0 : leftLength > rightLength; } @operator(">=") private static __gte(left: String, right: String): bool { return !this.__lt(left, right); } @operator("<") private static __lt(left: String, right: String): bool { if (changetype(left) == changetype(right)) return false; let rightLength = right.length; if (!rightLength) return false; let leftLength = left.length; if (!leftLength) return true; // @ts-ignore: string <-> String let res = compareImpl(left, 0, right, 0, min(leftLength, rightLength)); return res ? res < 0 : leftLength < rightLength; } @operator("<=") private static __lte(left: String, right: String): bool { return !this.__gt(left, right); } includes(search: String, start: i32 = 0): bool { return this.indexOf(search, start) != -1; } indexOf(search: String, start: i32 = 0): i32 { let searchLen = search.length; if (!searchLen) return 0; let len = this.length; if (!len) return -1; let searchStart = min(max(start, 0), len); for (len -= searchLen; searchStart <= len; ++searchStart) { // @ts-ignore: string <-> String if (!compareImpl(this, searchStart, search, 0, searchLen)) return searchStart; } return -1; } lastIndexOf(search: String, start: i32 = i32.MAX_VALUE): i32 { let searchLen = search.length; if (!searchLen) return this.length; let len = this.length; if (!len) return -1; let searchStart = min(max(start, 0), len - searchLen); for (; searchStart >= 0; --searchStart) { // @ts-ignore: string <-> String if (!compareImpl(this, searchStart, search, 0, searchLen)) return searchStart; } return -1; } // TODO: implement full locale comparison with locales and Collator options localeCompare(other: String): i32 { if (changetype(other) == changetype(this)) return 0; let alen = this.length; let blen = other.length; // @ts-ignore: string <-> String let res = compareImpl(this, 0, other, 0, min(alen, blen)); res = res ? res : alen - blen; // normalize to [-1, 1] range return i32(res > 0) - i32(res < 0); } startsWith(search: String, start: i32 = 0): bool { let len = this.length; let searchStart = min(max(start, 0), len); let searchLength = search.length; if (searchLength + searchStart > len) return false; // @ts-ignore: string <-> String return !compareImpl(this, searchStart, search, 0, searchLength); } substr(start: i32, length: i32 = i32.MAX_VALUE): String { // legacy let intStart: isize = start; let end: isize = length; let len: isize = this.length; if (intStart < 0) intStart = max(len + intStart, 0); let size = min(max(end, 0), len - intStart) << 1; if (size <= 0) return changetype(""); let out = changetype(__new(size, idof())); memory.copy(changetype(out), changetype(this) + (intStart << 1), size); return out; } substring(start: i32, end: i32 = i32.MAX_VALUE): String { let len: isize = this.length; let finalStart = min(max(start, 0), len); let finalEnd = min(max(end, 0), len); let fromPos = min(finalStart, finalEnd) << 1; let toPos = max(finalStart, finalEnd) << 1; let size = toPos - fromPos; if (!size) return changetype(""); if (!fromPos && toPos == len << 1) return this; let out = changetype(__new(size, idof())); memory.copy(changetype(out), changetype(this) + fromPos, size); return out; } trim(): String { let len = this.length; let size: usize = len << 1; while (size && isSpace(load(changetype(this) + size - 2))) { size -= 2; } let offset: usize = 0; while (offset < size && isSpace(load(changetype(this) + offset))) { offset += 2; size -= 2; } if (!size) return changetype(""); if (!offset && size == len << 1) return this; let out = changetype(__new(size, idof())); memory.copy(changetype(out), changetype(this) + offset, size); return out; } @inline trimLeft(): String { return this.trimStart(); } @inline trimRight(): String { return this.trimEnd(); } trimStart(): String { let size = this.length << 1; let offset: usize = 0; while (offset < size && isSpace(load(changetype(this) + offset))) { offset += 2; } if (!offset) return this; size -= offset; if (!size) return changetype(""); let out = changetype(__new(size, idof())); memory.copy(changetype(out), changetype(this) + offset, size); return out; } trimEnd(): String { let originalSize = this.length << 1; let size = originalSize; while (size && isSpace(load(changetype(this) + size - 2))) { size -= 2; } if (!size) return changetype(""); if (size == originalSize) return this; let out = changetype(__new(size, idof())); memory.copy(changetype(out), changetype(this), size); return out; } padStart(length: i32, pad: string = " "): String { let thisSize = this.length << 1; let targetSize = length << 1; let padSize = pad.length << 1; if (targetSize < thisSize || !padSize) return this; let prependSize = targetSize - thisSize; let out = changetype(__new(targetSize, idof())); if (prependSize > padSize) { let repeatCount = (prependSize - 2) / padSize; let restBase = repeatCount * padSize; let restSize = prependSize - restBase; memory.repeat(changetype(out), changetype(pad), padSize, repeatCount); memory.copy(changetype(out) + restBase, changetype(pad), restSize); } else { memory.copy(changetype(out), changetype(pad), prependSize); } memory.copy(changetype(out) + prependSize, changetype(this), thisSize); return out; } padEnd(length: i32, pad: string = " "): String { let thisSize = this.length << 1; let targetSize = length << 1; let padSize = pad.length << 1; if (targetSize < thisSize || !padSize) return this; let appendSize = targetSize - thisSize; let out = changetype(__new(targetSize, idof())); memory.copy(changetype(out), changetype(this), thisSize); if (appendSize > padSize) { let repeatCount = (appendSize - 2) / padSize; let restBase = repeatCount * padSize; let restSize = appendSize - restBase; memory.repeat(changetype(out) + thisSize, changetype(pad), padSize, repeatCount); memory.copy(changetype(out) + thisSize + restBase, changetype(pad), restSize); } else { memory.copy(changetype(out) + thisSize, changetype(pad), appendSize); } return out; } repeat(count: i32 = 0): String { let length = this.length; // Most browsers can't handle strings 1 << 28 chars or longer if (count < 0 || length * count > (1 << 28)) { throw new RangeError(E_INVALIDLENGTH); } if (count == 0 || !length) return changetype(""); if (count == 1) return this; let out = changetype(__new((length * count) << 1, idof())); memory.repeat(changetype(out), changetype(this), length << 1, count); return out; } replace(search: String, replacement: String): String { let len: usize = this.length; let slen: usize = search.length; if (len <= slen) { return len < slen ? this : select(replacement, this, search == this); } let index: isize = this.indexOf(search); if (~index) { let rlen: usize = replacement.length; len -= slen; let olen = len + rlen; if (olen) { let out = changetype(__new(olen << 1, idof())); memory.copy(changetype(out), changetype(this), index << 1); memory.copy( changetype(out) + (index << 1), changetype(replacement), rlen << 1 ); memory.copy( changetype(out) + ((index + rlen) << 1), changetype(this) + ((index + slen) << 1), (len - index) << 1 ); return out; } } return this; } replaceAll(search: String, replacement: String): String { let thisLen: usize = this.length; let searchLen: usize = search.length; if (thisLen <= searchLen) { return thisLen < searchLen ? this : select(replacement, this, search == this); } let replaceLen: usize = replacement.length; if (!searchLen) { if (!replaceLen) return this; // Special case: 'abc'.replaceAll('', '-') -> '-a-b-c-' let out = changetype(__new((thisLen + (thisLen + 1) * replaceLen) << 1, idof())); memory.copy(changetype(out), changetype(replacement), replaceLen << 1); let offset = replaceLen; for (let i: usize = 0; i < thisLen; ++i) { store( changetype(out) + (offset++ << 1), load(changetype(this) + (i << 1)) ); memory.copy( changetype(out) + (offset << 1), changetype(replacement), replaceLen << 1 ); offset += replaceLen; } return out; } let prev: isize = 0, next: isize = 0; if (searchLen == replaceLen) { // Fast path when search and replacement have same length let outSize = thisLen << 1; let out = changetype(__new(outSize, idof())); memory.copy(changetype(out), changetype(this), outSize); while (~(next = this.indexOf(search, prev))) { memory.copy(changetype(out) + (next << 1), changetype(replacement), replaceLen << 1); prev = next + searchLen; } return out; } let out: String | null = null, offset: usize = 0, outSize = thisLen; while (~(next = this.indexOf(search, prev))) { if (!out) out = changetype(__new(thisLen << 1, idof())); let chunk = next - prev; if (offset + chunk + replaceLen > outSize) { outSize <<= 1; out = changetype(__renew(changetype(out), outSize << 1)); } memory.copy( changetype(out) + (offset << 1), changetype(this) + (prev << 1), chunk << 1 ); offset += chunk; memory.copy( changetype(out) + (offset << 1), changetype(replacement), replaceLen << 1 ); offset += replaceLen; prev = next + searchLen; } if (out) { let rest = thisLen - prev; if (offset + rest > outSize) { outSize <<= 1; out = changetype(__renew(changetype(out), outSize << 1)); } if (rest) { memory.copy( changetype(out) + (offset << 1), changetype(this) + (prev << 1), rest << 1 ); } rest += offset; if (outSize > rest) { out = changetype(__renew(changetype(out), rest << 1)); } return out; } return this; } slice(start: i32, end: i32 = i32.MAX_VALUE): String { let len = this.length; start = start < 0 ? max(start + len, 0) : min(start, len); end = end < 0 ? max(end + len, 0) : min(end, len); len = end - start; if (len <= 0) return changetype(""); let out = changetype(__new(len << 1, idof())); memory.copy(changetype(out), changetype(this) + (start << 1), len << 1); return out; } split(separator: String | null = null, limit: i32 = i32.MAX_VALUE): String[] { if (!limit) return changetype(__newArray(0, alignof(), idof>())); if (changetype(separator) == 0) return [ this ]; let length: isize = this.length; let sepLen = changetype(separator).length; if (limit < 0) limit = i32.MAX_VALUE; if (!sepLen) { if (!length) return changetype(__newArray(0, alignof(), idof>())); // split by chars length = min(length, limit); let result = changetype(__newArray(length, alignof(), idof>())); // @ts-ignore: cast let resultStart = result.dataStart as usize; for (let i: isize = 0; i < length; ++i) { let charStr = changetype(__new(2, idof())); store(changetype(charStr), load(changetype(this) + (i << 1))); store(resultStart + (i << alignof()), changetype(charStr)); // result[i] = charStr __link(changetype(result), changetype(charStr), true); } return result; } else if (!length) { let result = changetype(__newArray(1, alignof(), idof>())); // @ts-ignore: cast store(result.dataStart as usize, changetype("")); // static "" return result; } let result = changetype(__newArray(0, alignof