/// 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(), idof>())); let end = 0, start = 0, i = 0; while (~(end = this.indexOf(changetype(separator), start))) { let len = end - start; if (len > 0) { let out = changetype(__new(len << 1, idof())); memory.copy(changetype(out), changetype(this) + (start << 1), len << 1); result.push(out); } else { result.push(changetype("")); } if (++i == limit) return result; start = end + sepLen; } if (!start) { // also means: loop above didn't do anything result.push(this); return result; } let len = length - start; if (len > 0) { let out = changetype(__new(len << 1, idof())); memory.copy(changetype(out), changetype(this) + (start << 1), len << 1); result.push(out); } else { result.push(changetype("")); // static "" } return result; } toLowerCase(): String { let len = this.length; if (!len) return this; let codes = changetype(__new(len * 2 * 2, idof())); let j: usize = 0; for (let i: usize = 0; i < len; ++i, ++j) { let c = load(changetype(this) + (i << 1)); if (isAscii(c)) { store(changetype(codes) + (j << 1), toLower8(c)); } else { // check and read surrogate pair if ((c - 0xD7FF < 0xDC00 - 0xD7FF) && i < len - 1) { let c1 = load(changetype(this) + (i << 1), 2); if (c1 - 0xDBFF < 0xE000 - 0xDBFF) { let c0 = c; c = (((c & 0x03FF) << 10) | (c1 & 0x03FF)) + 0x10000; ++i; if (c >= 0x20000) { store(changetype(codes) + (j << 1), c0 | (c1 << 16)); ++j; continue; } } } // check special casing for lower table. It has one ently so instead lookup we just inline this. if (c == 0x0130) { // 0x0130 -> [0x0069, 0x0307] store(changetype(codes) + (j << 1), (0x0307 << 16) | 0x0069); ++j; } else if (c == 0x03A3) { // 'Σ' // Σ maps to σ but except at the end of a word where it maps to ς let sigma = 0x03C3; // σ if (len > 1 && isFinalSigma(changetype(this), i, len)) { sigma = 0x03C2; // ς } store(changetype(codes) + (j << 1), sigma); } else if (c - 0x24B6 <= 0x24CF - 0x24B6) { // Range 0x24B6 <= c <= 0x24CF not covered by casemap and require special early handling store(changetype(codes) + (j << 1), c + 26); } else { let code = casemap(c, 0) & 0x1FFFFF; if (code < 0x10000) { store(changetype(codes) + (j << 1), code); } else { // store as surrogare pair code -= 0x10000; let lo = (code >>> 10) | 0xD800; let hi = (code & 0x03FF) | 0xDC00; store(changetype(codes) + (j << 1), lo | (hi << 16)); ++j; } } } } return changetype(__renew(changetype(codes), j << 1)); } toUpperCase(): String { let len = this.length; if (!len) return this; let codes = changetype(__new(len * 3 * 2, idof())); let specialsPtr = changetype(SPECIALS_UPPER); let specialsLen = SPECIALS_UPPER.length; let j: usize = 0; for (let i: usize = 0; i < len; ++i, ++j) { let c = load(changetype(this) + (i << 1)); if (isAscii(c)) { store(changetype(codes) + (j << 1), toUpper8(c)); } else { // check and read surrogate pair if ((c - 0xD7FF < 0xDC00 - 0xD7FF) && i < len - 1) { let c1 = load(changetype(this) + (i << 1), 2); if (c1 - 0xDBFF < 0xE000 - 0xDBFF) { let c0 = c; c = (((c & 0x03FF) << 10) | (c1 & 0x03FF)) + 0x10000; ++i; if (c >= 0x20000) { store(changetype(codes) + (j << 1), c0 | (c1 << 16)); ++j; continue; } } } // Range 0x24D0 <= c <= 0x24E9 not covered by casemap and require special early handling if (c - 0x24D0 <= 0x24E9 - 0x24D0) { // monkey patch store(changetype(codes) + (j << 1), c - 26); } else { let index: usize = -1; // Fast range check. See first and last rows in specialsUpper table if (c - 0x00DF <= 0xFB17 - 0x00DF) { index = bsearch(c, specialsPtr, specialsLen); } if (~index) { // load next 3 code points from row with `index` offset for specialsUpper table let ab = load(specialsPtr + (index << 1), 2); let cc = load(specialsPtr + (index << 1), 6); store(changetype(codes) + (j << 1), ab, 0); store(changetype(codes) + (j << 1), cc, 4); j += 1 + usize(cc != 0); } else { let code = casemap(c, 1) & 0x1FFFFF; if (code < 0x10000) { store(changetype(codes) + (j << 1), code); } else { // store as surrogare pair code -= 0x10000; let lo = (code >>> 10) | 0xD800; let hi = (code & 0x03FF) | 0xDC00; store(changetype(codes) + (j << 1), lo | (hi << 16)); ++j; } } } } } return changetype(__renew(changetype(codes), j << 1)); } toString(): String { return this; } } // @ts-ignore: nolib export type string = String; export function parseInt(str: string, radix: i32 = 0): f64 { return strtol(str, radix); } export function parseFloat(str: string): f64 { return strtod(str); } // Encoding helpers export namespace String { export namespace UTF8 { export const enum ErrorMode { WTF8, REPLACE, ERROR } export function byteLength(str: string, nullTerminated: bool = false): i32 { let strOff = changetype(str); let strEnd = strOff + changetype(changetype(str) - TOTAL_OVERHEAD).rtSize; let bufLen = i32(nullTerminated); while (strOff < strEnd) { let c1 = load(strOff); if (c1 < 128) { // @ts-ignore: cast if (nullTerminated & !c1) break; bufLen += 1; } else if (c1 < 2048) { bufLen += 2; } else { if ((c1 & 0xFC00) == 0xD800 && strOff + 2 < strEnd) { if ((load(strOff, 2) & 0xFC00) == 0xDC00) { bufLen += 4; strOff += 4; continue; } } bufLen += 3; } strOff += 2; } return bufLen; } export function encode(str: string, nullTerminated: bool = false, errorMode: ErrorMode = ErrorMode.WTF8): ArrayBuffer { let buf = changetype(__new(byteLength(str, nullTerminated), idof())); encodeUnsafe(changetype(str), str.length, changetype(buf), nullTerminated, errorMode); return buf; } // @ts-ignore: decorator @unsafe export function encodeUnsafe(str: usize, len: i32, buf: usize, nullTerminated: bool = false, errorMode: ErrorMode = ErrorMode.WTF8): usize { let strEnd = str + (len << 1); let bufOff = buf; while (str < strEnd) { let c1 = load(str); if (c1 < 128) { store(bufOff, c1); bufOff++; // @ts-ignore: cast if (nullTerminated & !c1) return bufOff - buf; } else if (c1 < 2048) { let b0 = c1 >> 6 | 192; let b1 = c1 & 63 | 128; store(bufOff, b1 << 8 | b0); bufOff += 2; } else { // D800: 11011 0 0000000000 Lead // DBFF: 11011 0 1111111111 // DC00: 11011 1 0000000000 Trail // DFFF: 11011 1 1111111111 // F800: 11111 0 0000000000 Mask // FC00: 11111 1 0000000000 if ((c1 & 0xF800) == 0xD800) { if (c1 < 0xDC00 && str + 2 < strEnd) { let c2 = load(str, 2); if ((c2 & 0xFC00) == 0xDC00) { c1 = 0x10000 + ((c1 & 0x03FF) << 10) | (c2 & 0x03FF); let b0 = c1 >> 18 | 240; let b1 = c1 >> 12 & 63 | 128; let b2 = c1 >> 6 & 63 | 128; let b3 = c1 & 63 | 128; store(bufOff, b3 << 24 | b2 << 16 | b1 << 8 | b0); bufOff += 4; str += 4; continue; } } if (errorMode != ErrorMode.WTF8) { // unlikely if (errorMode == ErrorMode.ERROR) throw new Error(E_UNPAIRED_SURROGATE); c1 = 0xFFFD; } } let b0 = c1 >> 12 | 224; let b1 = c1 >> 6 & 63 | 128; let b2 = c1 & 63 | 128; store(bufOff, b1 << 8 | b0); store(bufOff, b2, 2); bufOff += 3; } str += 2; } if (nullTerminated) { store(bufOff++, 0); } return bufOff - buf; } export function decode(buf: ArrayBuffer, nullTerminated: bool = false): String { return decodeUnsafe(changetype(buf), buf.byteLength, nullTerminated); } // @ts-ignore: decorator @unsafe export function decodeUnsafe(buf: usize, len: usize, nullTerminated: bool = false): String { let bufOff = buf; let bufEnd = buf + len; assert(bufEnd >= bufOff); // guard wraparound let str = changetype(__new(len << 1, idof())); // max is one u16 char per u8 byte let strOff = changetype(str); while (bufOff < bufEnd) { let u0 = load(bufOff); ++bufOff; if (!(u0 & 128)) { // @ts-ignore: cast if (nullTerminated & !u0) break; store(strOff, u0); } else { if (bufEnd == bufOff) break; let u1 = load(bufOff) & 63; ++bufOff; if ((u0 & 224) == 192) { store(strOff, (u0 & 31) << 6 | u1); } else { if (bufEnd == bufOff) break; let u2 = load(bufOff) & 63; ++bufOff; if ((u0 & 240) == 224) { u0 = (u0 & 15) << 12 | u1 << 6 | u2; } else { if (bufEnd == bufOff) break; u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | load(bufOff) & 63; ++bufOff; } if (u0 < 0x10000) { store(strOff, u0); } else { u0 -= 0x10000; let lo = u0 >> 10 | 0xD800; let hi = (u0 & 0x03FF) | 0xDC00; store(strOff, lo | (hi << 16)); strOff += 2; } } } strOff += 2; } return changetype(__renew(changetype(str), strOff - changetype(str))); } } export namespace UTF16 { export function byteLength(str: string): i32 { return changetype(changetype(str) - TOTAL_OVERHEAD).rtSize; } export function encode(str: string): ArrayBuffer { let buf = changetype(__new(byteLength(str), idof())); encodeUnsafe(changetype(str), str.length, changetype(buf)); return buf; } // @ts-ignore: decorator @unsafe export function encodeUnsafe(str: usize, len: i32, buf: usize): usize { let size = len << 1; memory.copy(buf, changetype(str), size); return size; } export function decode(buf: ArrayBuffer): String { return decodeUnsafe(changetype(buf), buf.byteLength); } // @ts-ignore: decorator @unsafe export function decodeUnsafe(buf: usize, len: usize): String { let str = changetype(__new(len &= ~1, idof())); memory.copy(changetype(str), buf, len); return str; } } } export class TemplateStringsArray extends Array { readonly raw: string[]; }