import { itoa_buffered, dtoa_buffered } from "util/number"; const MIN_BUFFER_LEN = 32; const MIN_BUFFER_SIZE: u32 = MIN_BUFFER_LEN << 1; const NEW_LINE_CHAR: u16 = 0x0A; // \n // @ts-ignore: decorator @inline function nextPowerOf2(n: u32): u32 { return 1 << 32 - clz(n - 1); } export class StringSink { protected buffer: ArrayBuffer; protected offset: u32 = 0; static withCapacity(capacity: i32): StringSink { return new StringSink("", capacity); } constructor(initial: string = "", capacity: i32 = MIN_BUFFER_LEN) { var size = initial.length << 1; this.buffer = changetype(__new( max(size, max(MIN_BUFFER_SIZE, capacity << 1)), idof()) ); if (size) { memory.copy( changetype(this.buffer), changetype(initial), size ); this.offset += size; } } get length(): i32 { return this.offset >> 1; } get capacity(): i32 { return this.buffer.byteLength >>> 1; } write(src: string, start: i32 = 0, end: i32 = i32.MAX_VALUE): void { let len = src.length as u32; if (start != 0 || end != i32.MAX_VALUE) { let from: i32; from = min(max(start, 0), len); end = min(max(end, 0), len); start = min(from, end); end = max(from, end); len = end - start; } if (!len) return; let size = len << 1; this.ensureCapacity(size); let offset = this.offset; memory.copy( changetype(this.buffer) + offset, changetype(src) + (start << 1), size ); this.offset = offset + size; } writeLn(src: string = "", start: i32 = 0, end: i32 = i32.MAX_VALUE): void { let len = src.length as u32; if (start != 0 || end != i32.MAX_VALUE) { let from: i32; from = min(max(start, 0), len); end = min(max(end, 0), len); start = min(from, end); end = max(from, end); len = end - start; } let size = len << 1; this.ensureCapacity(size + 2); let offset = this.offset; let dest = changetype(this.buffer) + offset; if (size) memory.copy(dest, changetype(src) + (start << 1), size); store(dest + size, NEW_LINE_CHAR); this.offset = offset + (size + 2); } writeCodePoint(code: i32): void { var hasSur = code > 0xFFFF; this.ensureCapacity(2 << i32(hasSur)); let offset = this.offset; let dest = changetype(this.buffer) + offset; if (!hasSur) { store(dest, code); this.offset = offset + 2; } else { assert(code <= 0x10FFFF); code -= 0x10000; let hi = (code & 0x03FF) | 0xDC00; let lo = code >>> 10 | 0xD800; store(dest, lo | hi << 16); this.offset = offset + 4; } } writeNumber(value: T): void { let offset = this.offset; if (isInteger()) { let maxCapacity = 0; // this also include size for sign if (sizeof() == 1) { maxCapacity = 4 << 1; } else if (sizeof() == 2) { maxCapacity = 6 << 1; } else if (sizeof() == 4) { maxCapacity = 11 << 1; } else if (sizeof() == 8) { maxCapacity = 21 << 1; } this.ensureCapacity(maxCapacity); offset += itoa_buffered( changetype(this.buffer) + offset, value ) << 1; } else { this.ensureCapacity(32 << 1); offset += dtoa_buffered( changetype(this.buffer) + offset, value ) << 1; } this.offset = offset; } reserve(capacity: i32, clear: bool = false): void { if (clear) this.offset = 0; this.buffer = changetype(__renew( changetype(this.buffer), max(this.offset, max(MIN_BUFFER_SIZE, capacity << 1)) )); } shrink(): void { this.buffer = changetype(__renew( changetype(this.buffer), max(this.offset, MIN_BUFFER_SIZE) )); } clear(): void { this.reserve(0, true); } toString(): string { let size = this.offset; if (!size) return ""; let out = changetype(__new(size, idof())); memory.copy(changetype(out), changetype(this.buffer), size); return out; } @inline protected ensureCapacity(deltaBytes: u32): void { let buffer = this.buffer; let newSize = this.offset + deltaBytes; if (newSize > buffer.byteLength) { this.buffer = changetype(__renew( changetype(buffer), nextPowerOf2(newSize) )); } } }