diff --git a/package.json b/package.json index f859d15..85efe2f 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,9 @@ "peerDependencies": { "tslib": "2" }, - "dependencies": {}, + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0" + }, "devDependencies": { "@types/benchmark": "^2.1.2", "@types/jest": "^29.5.12", diff --git a/src/buffers/Reader.ts b/src/buffers/Reader.ts index b2ee0b4..77552f7 100644 --- a/src/buffers/Reader.ts +++ b/src/buffers/Reader.ts @@ -1,115 +1 @@ -import {decodeUtf8} from './utf8/decodeUtf8'; -import type {IReader, IReaderResettable} from './types'; - -export class Reader implements IReader, IReaderResettable { - public uint8 = new Uint8Array([]); - public view = new DataView(this.uint8.buffer); - public x = 0; - - public reset(uint8: Uint8Array): void { - this.x = 0; - this.uint8 = uint8; - this.view = new DataView(uint8.buffer, uint8.byteOffset, uint8.length); - } - - /** - * Get current byte value without advancing the cursor. - */ - public peek(): number { - return this.view.getUint8(this.x); - } - - /** - * @deprecated Use peek() instead. - */ - public peak(): number { - return this.peek(); - } - - public skip(length: number): void { - this.x += length; - } - - public buf(size: number): Uint8Array { - const end = this.x + size; - const bin = this.uint8.subarray(this.x, end); - this.x = end; - return bin; - } - - public u8(): number { - return this.uint8[this.x++]; - // return this.view.getUint8(this.x++); - } - - public i8(): number { - return this.view.getInt8(this.x++); - } - - public u16(): number { - // const num = this.view.getUint16(this.x); - // this.x += 2; - // return num; - let x = this.x; - const num = (this.uint8[x++] << 8) + this.uint8[x++]; - this.x = x; - return num; - } - - public i16(): number { - const num = this.view.getInt16(this.x); - this.x += 2; - return num; - } - - public u32(): number { - const num = this.view.getUint32(this.x); - this.x += 4; - return num; - } - - public i32(): number { - const num = this.view.getInt32(this.x); - this.x += 4; - return num; - } - - public u64(): bigint { - const num = this.view.getBigUint64(this.x); - this.x += 8; - return num; - } - - public i64(): bigint { - const num = this.view.getBigInt64(this.x); - this.x += 8; - return num; - } - - public f32(): number { - const pos = this.x; - this.x += 4; - return this.view.getFloat32(pos); - } - - public f64(): number { - const pos = this.x; - this.x += 8; - return this.view.getFloat64(pos); - } - - public utf8(size: number): string { - const start = this.x; - this.x += size; - return decodeUtf8(this.uint8, start, size); - } - - public ascii(length: number): string { - const uint8 = this.uint8; - let str = ''; - const end = this.x + length; - for (let i = this.x; i < end; i++) str += String.fromCharCode(uint8[i]); - this.x = end; - return str; - } -} +export * from '@jsonjoy.com/buffers/lib/Reader'; diff --git a/src/buffers/Slice.ts b/src/buffers/Slice.ts index c0287b8..6a74afc 100644 --- a/src/buffers/Slice.ts +++ b/src/buffers/Slice.ts @@ -1,12 +1 @@ -export class Slice { - constructor( - public readonly uint8: Uint8Array, - public readonly view: DataView, - public readonly start: number, - public readonly end: number, - ) {} - - public subarray(): Uint8Array { - return this.uint8.subarray(this.start, this.end); - } -} +export * from '@jsonjoy.com/buffers/lib/Slice'; diff --git a/src/buffers/StreamingOctetReader.ts b/src/buffers/StreamingOctetReader.ts index 5370e7b..a58fc11 100644 --- a/src/buffers/StreamingOctetReader.ts +++ b/src/buffers/StreamingOctetReader.ts @@ -1,183 +1 @@ -const fromCharCode = String.fromCharCode; - -export class StreamingOctetReader { - protected readonly chunks: Uint8Array[] = []; - - /** Total size of all chunks. */ - protected chunkSize: number = 0; - - protected x: number = 0; - - public size(): number { - return this.chunkSize - this.x; - } - - public push(chunk: Uint8Array): void { - this.chunks.push(chunk); - this.chunkSize += chunk.length; - } - - protected assertSize(size: number): void { - if (size > this.size()) throw new RangeError('OUT_OF_BOUNDS'); - } - - public u8(): number { - this.assertSize(1); - const chunk = this.chunks[0]!; - let x = this.x; - const octet = chunk[x++]; - if (x === chunk.length) { - this.chunks.shift(); - this.chunkSize -= chunk.length; - x = 0; - } - this.x = x; - return octet; - } - - public u32(): number { - const octet0 = this.u8(); - const octet1 = this.u8(); - const octet2 = this.u8(); - const octet3 = this.u8(); - return (octet0 * 0x1000000 + (octet1 << 16) + (octet2 << 8)) | octet3; - } - - public copy(size: number, dst: Uint8Array, pos: number): void { - if (!size) return; - this.assertSize(size); - const chunk0 = this.chunks[0]!; - const size0 = Math.min(chunk0.length - this.x, size); - dst.set(chunk0.subarray(this.x, this.x + size0), pos); - size -= size0; - if (size <= 0) { - this.skipUnsafe(size0); - return; - } - let chunkIndex = 1; - while (size > 0) { - const chunk1 = this.chunks[chunkIndex]!; - const size1 = Math.min(chunk1.length, size); - dst.set(chunk1.subarray(0, size1), pos + size0); - size -= size1; - chunkIndex++; - } - this.skipUnsafe(size); - } - - public copyXor( - size: number, - dst: Uint8Array, - pos: number, - mask: [number, number, number, number], - maskIndex: number, - ): void { - if (!size) return; - this.assertSize(size); - const chunk0 = this.chunks[0]!; - let x = this.x; - const size0 = Math.min(chunk0.length - x, size); - const end = x + size0; - for (; x < end; ) dst[pos++] = chunk0[x++] ^ mask[maskIndex++ % 4]; - size -= size0; - if (size <= 0) { - this.skipUnsafe(size0); - return; - } - let chunkIndex = 1; - while (size > 0) { - const chunk1 = this.chunks[chunkIndex++]!; - const size1 = Math.min(chunk1.length, size); - for (let x = 0; x < size1; ) dst[pos++] = chunk1[x++] ^ mask[maskIndex++ % 4]; - size -= size1; - } - this.skipUnsafe(size); - } - - public buf(size: number): Uint8Array { - this.assertSize(size); - const buf = new Uint8Array(size); - this.copy(size, buf, 0); - return buf; - } - - public bufXor(size: number, mask: [number, number, number, number], maskIndex: number): Uint8Array { - this.assertSize(size); - const buf = new Uint8Array(size); - this.copyXor(size, buf, 0, mask, maskIndex); - return buf; - } - - public skipUnsafe(n: number): void { - if (!n) return; - const chunk = this.chunks[0]!; - const chunkLength = chunk.length; - const remaining = chunkLength - this.x; - if (remaining > n) { - this.x = this.x + n; - return; - } - this.x = 0; - this.chunks.shift(); - this.chunkSize -= chunkLength; - n -= remaining; - this.skipUnsafe(n); - } - - public skip(n: number): void { - this.assertSize(n); - this.skipUnsafe(n); - } - - public peek(): number { - this.assertSize(1); - return this.chunks[0]![this.x]; - } - - /** - * Get current byte value without advancing the cursor. - * @deprecated Use peek() instead. - */ - public peak(): number { - return this.peek(); - } - - public utf8(length: number, mask: [number, number, number, number], maskIndex: number): string { - this.assertSize(length); - let i = 0; - const points: number[] = []; - while (i < length) { - let code = this.u8() ^ mask[maskIndex++ % 4]; - i++; - if ((code & 0x80) !== 0) { - const octet2 = (this.u8() ^ mask[maskIndex++ % 4]) & 0x3f; - i++; - if ((code & 0xe0) === 0xc0) { - code = ((code & 0x1f) << 6) | octet2; - } else { - const octet3 = (this.u8() ^ mask[maskIndex++ % 4]) & 0x3f; - i++; - if ((code & 0xf0) === 0xe0) { - code = ((code & 0x1f) << 12) | (octet2 << 6) | octet3; - } else { - if ((code & 0xf8) === 0xf0) { - const octet4 = (this.u8() ^ mask[maskIndex++ % 4]) & 0x3f; - i++; - let unit = ((code & 0x07) << 0x12) | (octet2 << 0x0c) | (octet3 << 0x06) | octet4; - if (unit > 0xffff) { - unit -= 0x10000; - const unit0 = ((unit >>> 10) & 0x3ff) | 0xd800; - code = 0xdc00 | (unit & 0x3ff); - points.push(unit0); - } else { - code = unit; - } - } - } - } - } - points.push(code); - } - return fromCharCode.apply(String, points); - } -} +export * from '@jsonjoy.com/buffers/lib/StreamingOctetReader'; diff --git a/src/buffers/StreamingReader.ts b/src/buffers/StreamingReader.ts index 34caac1..037e5d3 100644 --- a/src/buffers/StreamingReader.ts +++ b/src/buffers/StreamingReader.ts @@ -1,187 +1 @@ -import {Writer} from './Writer'; -import {decodeUtf8} from './utf8/decodeUtf8'; -import type {IReader, IReaderResettable} from './types'; - -export class StreamingReader implements IReader, IReaderResettable { - protected readonly writer: Writer; - - /** - * Offset from the start of the buffer (x0 in Writer). - */ - protected dx = 0; - - constructor(allocSize: number = 16 * 1024) { - this.writer = new Writer(allocSize); - } - - /** - * Returns the number of bytes remaining in the buffer. - */ - public size(): number { - return this.writer.x - this.x; - } - - /** - * Assert that there is enough data in the buffer to read `size` bytes. - * - * @param size Number of bytes to read. - */ - protected assertSize(size: number): void { - if (size > this.size()) throw new RangeError('OUT_OF_BOUNDS'); - } - - /** - * Add a chunk of data to be decoded. The chunk is copied into the - * internal buffer, so you can reuse the chunk after calling this method; or - * this chunk can be neutered by the caller. - * - * @param uint8 `Uint8Array` chunk of data to be decoded. - */ - public push(uint8: Uint8Array): void { - this.writer.buf(uint8, uint8.length); - } - - /** - * Mark the current position as consumed. This will free up memory - * for reuse. - */ - public consume(): void { - this.writer.x0 += this.dx; - this.dx = 0; - } - - // ------------------------------------------------------------------ IReader - - public get uint8(): Uint8Array { - return this.writer.uint8; - } - - public get view(): DataView { - return this.writer.view; - } - - public get x(): number { - return this.writer.x0 + this.dx; - } - - public set x(x: number) { - this.dx = x - this.writer.x0; - } - - public peek(): number { - this.assertSize(1); - return this.view.getUint8(this.x); - } - - /** - * Get current byte value without advancing the cursor. - * @deprecated Use peek() instead. - */ - public peak(): number { - return this.peek(); - } - - public skip(length: number): void { - this.assertSize(length); - this.x += length; - } - - public buf(size: number): Uint8Array { - this.assertSize(size); - const end = this.x + size; - const bin = this.uint8.subarray(this.x, end); - this.x = end; - return bin; - } - - public u8(): number { - this.assertSize(1); - return this.view.getUint8(this.x++); - } - - public i8(): number { - this.assertSize(1); - return this.view.getInt8(this.x++); - } - - public u16(): number { - this.assertSize(2); - const num = this.view.getUint16(this.x); - this.x += 2; - return num; - } - - public i16(): number { - this.assertSize(2); - const num = this.view.getInt16(this.x); - this.x += 2; - return num; - } - - public u32(): number { - this.assertSize(4); - const num = this.view.getUint32(this.x); - this.x += 4; - return num; - } - - public i32(): number { - this.assertSize(4); - const num = this.view.getInt32(this.x); - this.x += 4; - return num; - } - - public u64(): bigint { - this.assertSize(8); - const num = this.view.getBigUint64(this.x); - this.x += 8; - return num; - } - - public i64(): bigint { - this.assertSize(8); - const num = this.view.getBigInt64(this.x); - this.x += 8; - return num; - } - - public f32(): number { - this.assertSize(4); - const pos = this.x; - this.x += 4; - return this.view.getFloat32(pos); - } - - public f64(): number { - this.assertSize(8); - const pos = this.x; - this.x += 8; - return this.view.getFloat64(pos); - } - - public utf8(size: number): string { - this.assertSize(size); - const start = this.x; - this.x += size; - return decodeUtf8(this.uint8, start, size); - } - - public ascii(length: number): string { - this.assertSize(length); - const uint8 = this.uint8; - let str = ''; - const end = this.x + length; - for (let i = this.x; i < end; i++) str += String.fromCharCode(uint8[i]); - this.x = end; - return str; - } - - // -------------------------------------------------------- IReaderResettable - - public reset(uint8: Uint8Array): void { - this.dx = 0; - this.writer.reset(); - this.push(uint8); - } -} +export * from '@jsonjoy.com/buffers/lib/StreamingReader'; diff --git a/src/buffers/Uint8ArrayCut.ts b/src/buffers/Uint8ArrayCut.ts index b57bfd6..b7e1d5f 100644 --- a/src/buffers/Uint8ArrayCut.ts +++ b/src/buffers/Uint8ArrayCut.ts @@ -1,7 +1 @@ -export class Uint8ArrayCut { - constructor( - public readonly uint8: Uint8Array, - public readonly start: number, - public readonly size: number, - ) {} -} +export * from '@jsonjoy.com/buffers/lib/Uint8ArrayCut'; diff --git a/src/buffers/Writer.ts b/src/buffers/Writer.ts index 8b96de2..183e387 100644 --- a/src/buffers/Writer.ts +++ b/src/buffers/Writer.ts @@ -1,269 +1 @@ -import {Slice} from './Slice'; -import {IWriterGrowable, IWriter} from './types'; - -const EMPTY_UINT8 = new Uint8Array([]); -const EMPTY_VIEW = new DataView(EMPTY_UINT8.buffer); - -const hasBuffer = typeof Buffer === 'function'; -const utf8Write = hasBuffer - ? (Buffer.prototype.utf8Write as (this: Uint8Array, str: string, pos: number, maxLength: number) => number) - : null; -const from = hasBuffer ? Buffer.from : null; -const textEncoder = typeof TextEncoder !== 'undefined' ? new TextEncoder() : null; - -/** - * Encoder class provides an efficient way to encode binary data. It grows the - * internal memory buffer automatically as more space is required. It is useful - * in cases when it is not known in advance the size of memory needed. - */ -export class Writer implements IWriter, IWriterGrowable { - /** @ignore */ - public uint8: Uint8Array; - /** @ignore */ - public view: DataView = EMPTY_VIEW; - /** @ignore */ - public x0: number = 0; - /** @ignore */ - public x: number = 0; - protected size: number; - - /** - * @param allocSize Number of bytes to allocate at a time when buffer ends. - */ - constructor(public allocSize: number = 64 * 1024) { - this.uint8 = new Uint8Array(allocSize); - this.size = allocSize; - this.view = new DataView(this.uint8.buffer); - } - - /** @ignore */ - protected grow(size: number) { - const x0 = this.x0; - const x = this.x; - const oldUint8 = this.uint8; - const newUint8 = new Uint8Array(size); - const view = new DataView(newUint8.buffer); - const activeSlice = oldUint8.subarray(x0, x); - newUint8.set(activeSlice, 0); - this.x = x - x0; - this.x0 = 0; - this.uint8 = newUint8; - this.size = size; - this.view = view; - } - - /** - * Make sure the internal buffer has enough space to write the specified number - * of bytes, otherwise resize the internal buffer to accommodate for more size. - * - * @param capacity Number of bytes. - */ - public ensureCapacity(capacity: number) { - const byteLength = this.size; - const remaining = byteLength - this.x; - if (remaining < capacity) { - const total = byteLength - this.x0; - const required = capacity - remaining; - const totalRequired = total + required; - this.grow(totalRequired <= this.allocSize ? this.allocSize : totalRequired * 2); - } - } - - /** @todo Consider renaming to "skip"? */ - public move(capacity: number) { - this.ensureCapacity(capacity); - this.x += capacity; - } - - public reset() { - this.x0 = this.x; - } - - /** - * Allocates a new {@link ArrayBuffer}, useful when the underlying - * {@link ArrayBuffer} cannot be shared between threads. - * - * @param size Size of memory to allocate. - */ - public newBuffer(size: number) { - const uint8 = (this.uint8 = new Uint8Array(size)); - this.size = size; - this.view = new DataView(uint8.buffer); - this.x = this.x0 = 0; - } - - /** - * @returns Encoded memory buffer contents. - */ - public flush(): Uint8Array { - const result = this.uint8.subarray(this.x0, this.x); - this.x0 = this.x; - return result; - } - - public flushSlice(): Slice { - const slice = new Slice(this.uint8, this.view, this.x0, this.x); - this.x0 = this.x; - return slice; - } - - public u8(char: number) { - this.ensureCapacity(1); - this.uint8[this.x++] = char; - } - - public u16(word: number) { - this.ensureCapacity(2); - this.view.setUint16(this.x, word); - this.x += 2; - } - - public u32(dword: number) { - this.ensureCapacity(4); - this.view.setUint32(this.x, dword); - this.x += 4; - } - - public i32(dword: number) { - this.ensureCapacity(4); - this.view.setInt32(this.x, dword); - this.x += 4; - } - - public u64(qword: number | bigint) { - this.ensureCapacity(8); - this.view.setBigUint64(this.x, BigInt(qword)); - this.x += 8; - } - - public f64(float: number) { - this.ensureCapacity(8); - this.view.setFloat64(this.x, float); - this.x += 8; - } - - public u8u16(u8: number, u16: number) { - this.ensureCapacity(3); - let x = this.x; - this.uint8[x++] = u8; - this.uint8[x++] = u16 >>> 8; - this.uint8[x++] = u16 & 0xff; - this.x = x; - } - - public u8u32(u8: number, u32: number) { - this.ensureCapacity(5); - let x = this.x; - this.uint8[x++] = u8; - this.view.setUint32(x, u32); - this.x = x + 4; - } - - public u8u64(u8: number, u64: number | bigint) { - this.ensureCapacity(9); - let x = this.x; - this.uint8[x++] = u8; - this.view.setBigUint64(x, BigInt(u64)); - this.x = x + 8; - } - - public u8f32(u8: number, f32: number) { - this.ensureCapacity(5); - let x = this.x; - this.uint8[x++] = u8; - this.view.setFloat32(x, f32); - this.x = x + 4; - } - - public u8f64(u8: number, f64: number) { - this.ensureCapacity(9); - let x = this.x; - this.uint8[x++] = u8; - this.view.setFloat64(x, f64); - this.x = x + 8; - } - - public buf(buf: Uint8Array, length: number): void { - this.ensureCapacity(length); - const x = this.x; - this.uint8.set(buf, x); - this.x = x + length; - } - - /** - * Encodes string as UTF-8. You need to call .ensureCapacity(str.length * 4) - * before calling - * - * @param str String to encode as UTF-8. - * @returns The number of bytes written - */ - public utf8(str: string): number { - const maxLength = str.length * 4; - if (maxLength < 168) return this.utf8Native(str); - if (utf8Write) { - const writeLength = utf8Write.call(this.uint8, str, this.x, maxLength); - this.x += writeLength; - return writeLength; - } else if (from) { - const uint8 = this.uint8; - const offset = uint8.byteOffset + this.x; - const buf = from(uint8.buffer).subarray(offset, offset + maxLength); - const writeLength = buf.write(str, 0, maxLength, 'utf8'); - this.x += writeLength; - return writeLength; - } else if (maxLength > 1024 && textEncoder) { - const writeLength = textEncoder!.encodeInto(str, this.uint8.subarray(this.x, this.x + maxLength)).written!; - this.x += writeLength; - return writeLength; - } - return this.utf8Native(str); - } - - public utf8Native(str: string): number { - const length = str.length; - const uint8 = this.uint8; - let offset = this.x; - let pos = 0; - while (pos < length) { - let value = str.charCodeAt(pos++); - if ((value & 0xffffff80) === 0) { - uint8[offset++] = value; - continue; - } else if ((value & 0xfffff800) === 0) { - uint8[offset++] = ((value >> 6) & 0x1f) | 0xc0; - } else { - if (value >= 0xd800 && value <= 0xdbff) { - if (pos < length) { - const extra = str.charCodeAt(pos); - if ((extra & 0xfc00) === 0xdc00) { - pos++; - value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; - } - } - } - if ((value & 0xffff0000) === 0) { - uint8[offset++] = ((value >> 12) & 0x0f) | 0xe0; - uint8[offset++] = ((value >> 6) & 0x3f) | 0x80; - } else { - uint8[offset++] = ((value >> 18) & 0x07) | 0xf0; - uint8[offset++] = ((value >> 12) & 0x3f) | 0x80; - uint8[offset++] = ((value >> 6) & 0x3f) | 0x80; - } - } - uint8[offset++] = (value & 0x3f) | 0x80; - } - const writeLength = offset - this.x; - this.x = offset; - return writeLength; - } - - public ascii(str: string): void { - const length = str.length; - this.ensureCapacity(length); - const uint8 = this.uint8; - let x = this.x; - let pos = 0; - while (pos < length) uint8[x++] = str.charCodeAt(pos++); - this.x = x; - } -} +export * from '@jsonjoy.com/buffers/lib/Writer'; diff --git a/src/buffers/__bench__/bench.decodeUtf8.ts b/src/buffers/__bench__/bench.decodeUtf8.ts deleted file mode 100644 index 3c091cc..0000000 --- a/src/buffers/__bench__/bench.decodeUtf8.ts +++ /dev/null @@ -1,108 +0,0 @@ -// yarn build && npx ts-node src/buffers/__bench__/bench.decodeUtf8.ts - -import {runBenchmark} from '../../__bench__/runBenchmark'; - -const prepare = (str: string) => { - const buf = Buffer.from(str); - const arr = new Uint8Array(buf.length); - for (let i = 0; i < buf.length; i++) arr[i] = buf[i]; - return arr; -}; - -const runner = (v: number, name: string) => ({ - name: `${name} (v${v})`, - setup: () => { - const decode = require('../../../lib/buffers/utf8/decodeUtf8/v' + v).default; - return (data: any) => decode(data, 0, data.length); - }, -}); - -const benchmark = { - name: 'decodeUtf8', - warmup: 1000, - payloads: [ - // { - // name: (buf) => `Single character, ${buf.length} bytes`, - // data: prepare('a'), - // test: () => 'a', - // }, - // { - // name: (buf) => `"Hello", ${buf.length} bytes`, - // data: prepare('Hello'), - // test: () => 'Hello', - // }, - // { - // name: (buf) => `Short text with emoji, ${buf.length} bytes`, - // data: prepare('Hi, Mike 👋!'), - // test: () => 'Hi, Mike 👋!', - // }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('a'.repeat(2)), - test: () => 'a'.repeat(2), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('a'.repeat(4)), - test: () => 'a'.repeat(4), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('a'.repeat(8)), - test: () => 'a'.repeat(8), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('abcd'.repeat(3)), - test: () => 'abcd'.repeat(3), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('abcd'.repeat(4)), - test: () => 'abcd'.repeat(4), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('abcd'.repeat(8)), - test: () => 'abcd'.repeat(8), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('abcd'.repeat(16)), - test: () => 'abcd'.repeat(16), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('abcd'.repeat(32)), - test: () => 'abcd'.repeat(32), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('abcd'.repeat(64)), - test: () => 'abcd'.repeat(64), - }, - { - name: (buf: any) => `Repeating characters, ${buf.length} bytes`, - data: prepare('abcd'.repeat(128)), - test: () => 'abcd'.repeat(128), - }, - ], - runners: [ - // runner(1, 'JS with buffering in array'), - // runner(2, 'Buffer.prototype.utf8Slice'), - // runner(3, 'Buffer.from(arr).slice'), - // runner(4, 'Buffer.from(arr).subarray'), - // runner(5, 'JS with string concatenation'), - // runner(6, 'TextDecoder'), - // runner(7, 'JS with buffering in array, no flushing'), - // runner(8, 'JS with buffering in array, small buffer'), - // runner(9, 'JS with buffering in array, variable reuse'), - // runner(10, 'JS with string concatenation, variable reuse'), - runner(19, 'json-pack-napi'), - runner(11, 'utf8Slice'), - // runner(12, 'from(arr).subarray'), - // runner(13, 'composition'), - ], -}; - -runBenchmark(benchmark); diff --git a/src/buffers/__tests__/StreamingReader.spec.ts b/src/buffers/__tests__/StreamingReader.spec.ts deleted file mode 100644 index 420dbfa..0000000 --- a/src/buffers/__tests__/StreamingReader.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {StreamingReader} from '../StreamingReader'; - -test('can push and read', () => { - const reader = new StreamingReader(4); - reader.push(new Uint8Array([1, 2, 3])); - expect(reader.u8()).toBe(1); - reader.push(new Uint8Array([4, 5, 6])); - expect(reader.u8()).toBe(2); - expect(reader.u8()).toBe(3); - expect(reader.u8()).toBe(4); - expect(reader.u8()).toBe(5); - expect(reader.u8()).toBe(6); -}); - -test('throws RangeError when reading out of bounds', () => { - const reader = new StreamingReader(2); - reader.push(new Uint8Array([1, 2, 3])); - reader.u16(); - reader.u8(); - expect(() => reader.u8()).toThrow(RangeError); -}); - -test('throws RangeError when reading out of bounds - 2', () => { - const reader = new StreamingReader(); - reader.push(new Uint8Array([1, 2, 3])); - reader.u16(); - expect(() => reader.u16()).toThrow(RangeError); -}); - -test('throws RangeError when reading out of bounds - 3', () => { - const reader = new StreamingReader(4); - reader.push(new Uint8Array([1, 2, 3])); - reader.u16(); - reader.push(new Uint8Array([4, 5])); - expect(() => reader.u32()).toThrow(RangeError); -}); - -test('size shrinks as data is read', () => { - const reader = new StreamingReader(4); - expect(reader.size()).toBe(0); - reader.push(new Uint8Array([1, 2, 3])); - expect(reader.size()).toBe(3); - expect(reader.u8()).toBe(1); - expect(reader.size()).toBe(2); - reader.push(new Uint8Array([4, 5, 6])); - expect(reader.size()).toBe(5); - expect(reader.u8()).toBe(2); - expect(reader.size()).toBe(4); - expect(reader.u8()).toBe(3); - expect(reader.size()).toBe(3); - expect(reader.u8()).toBe(4); - expect(reader.size()).toBe(2); - expect(reader.u8()).toBe(5); - expect(reader.size()).toBe(1); - expect(reader.u8()).toBe(6); - expect(reader.size()).toBe(0); -}); diff --git a/src/buffers/__tests__/isFloat32.spec.ts b/src/buffers/__tests__/isFloat32.spec.ts deleted file mode 100644 index 34a2104..0000000 --- a/src/buffers/__tests__/isFloat32.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import {isFloat32} from '../isFloat32'; - -test('returns true for a float32', () => { - expect(isFloat32(1.5)).toBe(true); -}); - -test('returns true for a float64', () => { - expect(isFloat32(1.1)).toBe(false); -}); diff --git a/src/buffers/__tests__/peak-peek.spec.ts b/src/buffers/__tests__/peak-peek.spec.ts deleted file mode 100644 index ac25dd6..0000000 --- a/src/buffers/__tests__/peak-peek.spec.ts +++ /dev/null @@ -1,119 +0,0 @@ -import {Reader} from '../Reader'; -import {StreamingReader} from '../StreamingReader'; -import {StreamingOctetReader} from '../StreamingOctetReader'; - -describe('peak() and peek() methods', () => { - describe('Reader', () => { - test('peak() returns current byte without advancing cursor', () => { - const reader = new Reader(); - reader.reset(new Uint8Array([1, 2, 3, 4, 5])); - expect(reader.x).toBe(0); - expect(reader.peak()).toBe(1); - expect(reader.x).toBe(0); // cursor should not advance - - reader.u8(); // advance cursor - expect(reader.x).toBe(1); - expect(reader.peak()).toBe(2); - expect(reader.x).toBe(1); // cursor should not advance - }); - - test('peek() returns current byte without advancing cursor', () => { - const reader = new Reader(); - reader.reset(new Uint8Array([1, 2, 3, 4, 5])); - - expect(reader.x).toBe(0); - expect(reader.peek()).toBe(1); - expect(reader.x).toBe(0); // cursor should not advance - - reader.u8(); // advance cursor - expect(reader.x).toBe(1); - expect(reader.peek()).toBe(2); - expect(reader.x).toBe(1); // cursor should not advance - }); - - test('peak() and peek() return the same value', () => { - const reader = new Reader(); - reader.reset(new Uint8Array([42, 100, 255])); - - expect(reader.peak()).toBe(reader.peek()); - reader.u8(); // advance cursor - expect(reader.peak()).toBe(reader.peek()); - reader.u8(); // advance cursor - expect(reader.peak()).toBe(reader.peek()); - }); - }); - - describe('StreamingReader', () => { - test('peak() returns current byte without advancing cursor', () => { - const reader = new StreamingReader(); - reader.push(new Uint8Array([10, 20, 30])); - - expect(reader.x).toBe(0); - expect(reader.peak()).toBe(10); - expect(reader.x).toBe(0); // cursor should not advance - - reader.u8(); // advance cursor - expect(reader.x).toBe(1); - expect(reader.peak()).toBe(20); - expect(reader.x).toBe(1); // cursor should not advance - }); - - test('peek() returns current byte without advancing cursor', () => { - const reader = new StreamingReader(); - reader.push(new Uint8Array([10, 20, 30])); - - expect(reader.x).toBe(0); - expect(reader.peek()).toBe(10); - expect(reader.x).toBe(0); // cursor should not advance - - reader.u8(); // advance cursor - expect(reader.x).toBe(1); - expect(reader.peek()).toBe(20); - expect(reader.x).toBe(1); // cursor should not advance - }); - - test('peak() and peek() return the same value', () => { - const reader = new StreamingReader(); - reader.push(new Uint8Array([42, 100, 255])); - - expect(reader.peak()).toBe(reader.peek()); - reader.u8(); // advance cursor - expect(reader.peak()).toBe(reader.peek()); - reader.u8(); // advance cursor - expect(reader.peak()).toBe(reader.peek()); - }); - }); - - describe('StreamingOctetReader', () => { - test('peak() returns current byte without advancing cursor', () => { - const reader = new StreamingOctetReader(); - reader.push(new Uint8Array([100, 200, 150])); - - expect(reader.peak()).toBe(100); - - reader.u8(); // advance cursor internally - expect(reader.peak()).toBe(200); - }); - - test('peek() returns current byte without advancing cursor', () => { - const reader = new StreamingOctetReader(); - reader.push(new Uint8Array([100, 200, 150])); - - expect(reader.peek()).toBe(100); - - reader.u8(); // advance cursor internally - expect(reader.peek()).toBe(200); - }); - - test('peak() and peek() return the same value', () => { - const reader = new StreamingOctetReader(); - reader.push(new Uint8Array([42, 100, 255])); - - expect(reader.peak()).toBe(reader.peek()); - reader.u8(); // advance cursor - expect(reader.peak()).toBe(reader.peek()); - reader.u8(); // advance cursor - expect(reader.peak()).toBe(reader.peek()); - }); - }); -}); diff --git a/src/buffers/b.ts b/src/buffers/b.ts index 003f912..4a7f8d9 100644 --- a/src/buffers/b.ts +++ b/src/buffers/b.ts @@ -1 +1 @@ -export const b = (...args: number[]) => new Uint8Array(args); +export * from '@jsonjoy.com/buffers/lib/b'; diff --git a/src/buffers/bufferToUint8Array.ts b/src/buffers/bufferToUint8Array.ts index 63598b6..c26dc87 100644 --- a/src/buffers/bufferToUint8Array.ts +++ b/src/buffers/bufferToUint8Array.ts @@ -1 +1 @@ -export const bufferToUint8Array = (buf: Buffer): Uint8Array => new Uint8Array(buf.buffer, buf.byteOffset, buf.length); +export * from '@jsonjoy.com/buffers/lib/bufferToUint8Array'; diff --git a/src/buffers/cmpUint8Array.ts b/src/buffers/cmpUint8Array.ts index 694c027..5f3fc07 100644 --- a/src/buffers/cmpUint8Array.ts +++ b/src/buffers/cmpUint8Array.ts @@ -1,6 +1 @@ -export const cmpUint8Array = (a: Uint8Array, b: Uint8Array): boolean => { - const length = a.length; - if (length !== b.length) return false; - for (let i = 0; i < length; i++) if (a[i] !== b[i]) return false; - return true; -}; +export * from '@jsonjoy.com/buffers/lib/cmpUint8Array'; diff --git a/src/buffers/cmpUint8Array2.ts b/src/buffers/cmpUint8Array2.ts index 344ec1e..b0a1be4 100644 --- a/src/buffers/cmpUint8Array2.ts +++ b/src/buffers/cmpUint8Array2.ts @@ -1,18 +1 @@ -/** - * Compares two `Uint8Arrays` byte-by-byte. Returns a negative number if `a` is - * less than `b`, a positive number if `a` is greater than `b`, or 0 if `a` is - * equal to `b`. - * - * @returns A negative number if a is less than b, a positive number if a is - * greater than b, or 0 if a is equal to b. - */ -export const cmpUint8Array2 = (a: Uint8Array, b: Uint8Array): number => { - const len1 = a.length; - const len2 = b.length; - const len = Math.min(len1, len2); - for (let i = 0; i < len; i++) { - const diffChar = a[i] - b[i]; - if (diffChar !== 0) return diffChar; - } - return len1 - len2; -}; +export * from '@jsonjoy.com/buffers/lib/cmpUint8Array2'; diff --git a/src/buffers/cmpUint8Array3.ts b/src/buffers/cmpUint8Array3.ts index 950c358..64a149f 100644 --- a/src/buffers/cmpUint8Array3.ts +++ b/src/buffers/cmpUint8Array3.ts @@ -1,19 +1 @@ -/** - * Compares two `Uint8Arrays`, first by length, then by each byte. Returns a - * negative number if `a` is less than `b`, a positive number if `a` is greater - * than `b`, or 0 if `a` is equal to `b`. - * - * @returns A negative number if a is less than b, a positive number if a is - * greater than b, or 0 if a is equal to b. - */ -export const cmpUint8Array3 = (a: Uint8Array, b: Uint8Array): number => { - const len1 = a.length; - const len2 = b.length; - const diff = len1 - len2; - if (diff !== 0) return diff; - for (let i = 0; i < len1; i++) { - const diff = a[i] - b[i]; - if (diff !== 0) return diff; - } - return 0; -}; +export * from '@jsonjoy.com/buffers/lib/cmpUint8Array3'; diff --git a/src/buffers/concat.ts b/src/buffers/concat.ts index 205bad1..fec1502 100644 --- a/src/buffers/concat.ts +++ b/src/buffers/concat.ts @@ -1,31 +1 @@ -export const concat = (a: Uint8Array, b: Uint8Array): Uint8Array => { - const res = new Uint8Array(a.length + b.length); - res.set(a); - res.set(b, a.length); - return res; -}; - -export const concatList = (list: Uint8Array[]): Uint8Array => { - const length = list.length; - let size = 0, - offset = 0; - for (let i = 0; i < length; i++) size += list[i].length; - const res = new Uint8Array(size); - for (let i = 0; i < length; i++) { - const item = list[i]; - res.set(item, offset); - offset += item.length; - } - return res; -}; - -export const listToUint8 = (list: Uint8Array[]): Uint8Array => { - switch (list.length) { - case 0: - return new Uint8Array(0); - case 1: - return list[0]; - default: - return concatList(list); - } -}; +export * from '@jsonjoy.com/buffers/lib/concat'; diff --git a/src/buffers/copy.ts b/src/buffers/copy.ts index d779133..2ae8ba2 100644 --- a/src/buffers/copy.ts +++ b/src/buffers/copy.ts @@ -1,5 +1 @@ -export const copy = (arr: T): T => { - const dupe = new Uint8Array(arr.length) as T; - dupe.set(arr); - return dupe; -}; +export * from '@jsonjoy.com/buffers/lib/copy'; diff --git a/src/buffers/f16.ts b/src/buffers/f16.ts index 51a4150..1674240 100644 --- a/src/buffers/f16.ts +++ b/src/buffers/f16.ts @@ -1,16 +1 @@ -const pow = Math.pow; - -export const decodeF16 = (binary: number): number => { - const exponent = (binary & 0x7c00) >> 10; - const fraction = binary & 0x03ff; - return ( - (binary >> 15 ? -1 : 1) * - (exponent - ? exponent === 0x1f - ? fraction - ? NaN - : Infinity - : pow(2, exponent - 15) * (1 + fraction / 0x400) - : 6.103515625e-5 * (fraction / 0x400)) - ); -}; +export * from '@jsonjoy.com/buffers/lib/f16'; diff --git a/src/buffers/isArrayBuffer.ts b/src/buffers/isArrayBuffer.ts index 998e906..133ce89 100644 --- a/src/buffers/isArrayBuffer.ts +++ b/src/buffers/isArrayBuffer.ts @@ -1,3 +1 @@ -export const isArrayBuffer = (value: unknown): value is ArrayBuffer => { - return value instanceof ArrayBuffer || toString.call(value) === '[object ArrayBuffer]'; -}; +export * from '@jsonjoy.com/buffers/lib/isArrayBuffer'; diff --git a/src/buffers/isFloat32.ts b/src/buffers/isFloat32.ts index 26e3823..33f9f9e 100644 --- a/src/buffers/isFloat32.ts +++ b/src/buffers/isFloat32.ts @@ -1,6 +1 @@ -const view = new DataView(new ArrayBuffer(4)); - -export const isFloat32 = (n: number): boolean => { - view.setFloat32(0, n); - return n === view.getFloat32(0); -}; +export * from '@jsonjoy.com/buffers/lib/isFloat32'; diff --git a/src/buffers/isUint8Array.ts b/src/buffers/isUint8Array.ts index b86a8b0..fd3453a 100644 --- a/src/buffers/isUint8Array.ts +++ b/src/buffers/isUint8Array.ts @@ -1,4 +1 @@ -export const isUint8Array = - typeof Buffer === 'function' - ? (x: unknown): x is Uint8Array => x instanceof Uint8Array || Buffer.isBuffer(x) - : (x: unknown): x is Uint8Array => x instanceof Uint8Array; +export * from '@jsonjoy.com/buffers/lib/isUint8Array'; diff --git a/src/buffers/printOctets.ts b/src/buffers/printOctets.ts index 9e5c01f..0a73e4c 100644 --- a/src/buffers/printOctets.ts +++ b/src/buffers/printOctets.ts @@ -1,14 +1 @@ -export const printOctets = (octets: Uint8Array, max: number = 16): string => { - let str = ''; - if (!octets.length) return str; - if (octets[0] < 16) str += '0'; - str += octets[0].toString(16); - for (let i = 1; i < octets.length && i < max; i++) { - const n = octets[i]; - str += ' '; - if (n < 16) str += '0'; - str += n.toString(16); - } - if (octets.length > max) str += `… (${octets.length - max} more)`; - return str; -}; +export * from '@jsonjoy.com/buffers/lib/printOctets'; diff --git a/src/buffers/strings.ts b/src/buffers/strings.ts index f4ac5b7..1fddef1 100644 --- a/src/buffers/strings.ts +++ b/src/buffers/strings.ts @@ -1,16 +1 @@ -import {bufferToUint8Array} from './bufferToUint8Array'; - -export const ascii = (txt: TemplateStringsArray | string | [string]): Uint8Array => { - if (typeof txt === 'string') return ascii([txt]); - [txt] = txt; - const len = txt.length; - const res = new Uint8Array(len); - for (let i = 0; i < len; i++) res[i] = txt.charCodeAt(i); - return res; -}; - -export const utf8 = (txt: TemplateStringsArray | [string] | string): Uint8Array => { - if (typeof txt === 'string') return utf8([txt]); - [txt] = txt; - return bufferToUint8Array(Buffer.from(txt, 'utf8')); -}; +export * from '@jsonjoy.com/buffers/lib/strings'; diff --git a/src/buffers/toBuf.ts b/src/buffers/toBuf.ts index 576f8a5..4f73b58 100644 --- a/src/buffers/toBuf.ts +++ b/src/buffers/toBuf.ts @@ -1,8 +1 @@ -import {encode} from './utf8/encode'; - -export const toBuf = (str: string): Uint8Array => { - const maxLength = str.length * 4; - const arr = new Uint8Array(maxLength); - const strBufferLength = encode(arr, str, 0, maxLength); - return arr.slice(0, strBufferLength); -}; +export * from '@jsonjoy.com/buffers/lib/toBuf'; diff --git a/src/buffers/toUint8Array.ts b/src/buffers/toUint8Array.ts index 8e2c916..0cdf3ec 100644 --- a/src/buffers/toUint8Array.ts +++ b/src/buffers/toUint8Array.ts @@ -1,10 +1 @@ -export const toUint8Array = (data: unknown): Uint8Array => { - if (data instanceof Uint8Array) return data; - if (data instanceof ArrayBuffer) return new Uint8Array(data); - if (Array.isArray(data)) return new Uint8Array(data); - if (typeof Buffer === 'function') { - if (Buffer.isBuffer(data)) return data; - return Buffer.from(data as any); - } - throw new Error('UINT8ARRAY_INCOMPATIBLE'); -}; +export * from '@jsonjoy.com/buffers/lib/toUint8Array'; diff --git a/src/buffers/types.ts b/src/buffers/types.ts index 2345713..dc3ea32 100644 --- a/src/buffers/types.ts +++ b/src/buffers/types.ts @@ -1,132 +1 @@ -import type {Slice} from './Slice'; - -export interface IWriter { - /** - * Uint8Array view of the current memory buffer. - */ - uint8: Uint8Array; - - /** - * DataView view of the current memory buffer. - */ - view: DataView; - - /** - * Position where last flush happened. - */ - x0: number; - - /** - * Current position in the internal buffer. - */ - x: number; - - u8(char: number): void; - u16(word: number): void; - u32(dword: number): void; - i32(dword: number): void; - u64(qword: number | bigint): void; - u8u16(u8: number, u16: number): void; - u8u32(u8: number, u32: number): void; - u8u64(u8: number, u64: number | bigint): void; - u8f32(u8: number, f64: number): void; - u8f64(u8: number, f64: number): void; - f64(dword: number): void; - - /** - * Write contents of a buffer. - * - * @param buf Buffer to copy from. - * @param length Number of octets to copy. - */ - buf(buf: Uint8Array, length: number): void; - - /** - * Write string as UTF-8. You need to call .ensureCapacity(str.length * 4) - * before calling - * - * @param str JavaScript string to encode as UTF-8 byte sequence. - */ - utf8(str: string): number; - - ascii(str: string): void; -} - -export interface IWriterGrowable { - /** @deprecated */ - reset(): void; - - /** - * Calling this method might reset the internal buffer. So, your references - * (such as `x`, `uint8`, `view`) to the internal buffer might become invalid. - * - * @param capacity How many octets to ensure are available after `x`. - */ - ensureCapacity(capacity: number): void; - move(length: number): void; - flush(): Uint8Array; - flushSlice(): Slice; - newBuffer(size: number): void; -} - -export interface IReaderBase { - /** Get current byte value without advancing the cursor. */ - peek(): number; - - /** - * Get current byte value without advancing the cursor. - * @deprecated Use peek() instead. - */ - peak(): number; - - /** Advance the cursor given number of octets. */ - skip(length: number): void; - - /** - * Create a new Uint8Array view of provided length starting at - * the current cursor position. - */ - buf(size: number): Uint8Array; - - u8(): number; - i8(): number; - u16(): number; - i16(): number; - u32(): number; - u64(): bigint; - i64(): bigint; - i32(): number; - f32(): number; - f64(): number; - - /** - * Decode a UTF-8 string. - * - * @param size Length of the string. - */ - utf8(size: number): string; - - ascii(length: number): string; -} - -export interface IReader extends IReaderBase { - /** - * Uint8Array view of the current memory buffer. - */ - uint8: Uint8Array; - - /** - * DataView view of the current memory buffer. - */ - view: DataView; - - /** - * Cursor in the current memory buffer. - */ - x: number; -} - -export interface IReaderResettable { - /** Set a new underlying buffer and reset cursor position to 0. */ - reset(uint8: Uint8Array): void; -} +export * from '@jsonjoy.com/buffers/lib/types'; diff --git a/src/buffers/utf8/CachedUtf8Decoder.ts b/src/buffers/utf8/CachedUtf8Decoder.ts index caedc8c..b6f4342 100644 --- a/src/buffers/utf8/CachedUtf8Decoder.ts +++ b/src/buffers/utf8/CachedUtf8Decoder.ts @@ -1,63 +1 @@ -import decodeUtf8 from './decodeUtf8/v10'; - -let x = 1 + Math.round(Math.random() * ((-1 >>> 0) - 1)); - -/** Generate a random 32-bit unsigned integer in the specified [min, max] range. */ -function randomU32(min: number, max: number): number { - x ^= x << 13; - x ^= x >>> 17; - x ^= x << 5; - return ((x >>> 0) % (max - min + 1)) + min; -} - -class CacheItem { - constructor( - public readonly bytes: Uint8Array, - public readonly value: string, - ) {} -} - -const enum CONST { - MAX_CACHED_STR_LEN = 31, - MAX_RECORDS_PER_SIZE = 16, -} - -export class CachedUtf8Decoder { - private readonly caches: CacheItem[][]; - - constructor() { - this.caches = []; - for (let i = 0; i < CONST.MAX_CACHED_STR_LEN; i++) this.caches.push([]); - } - - private get(bytes: Uint8Array, offset: number, size: number): string | null { - const records = this.caches[size - 1]!; - const len = records.length; - FIND_CHUNK: for (let i = 0; i < len; i++) { - const record = records[i]; - const recordBytes = record.bytes; - for (let j = 0; j < size; j++) if (recordBytes[j] !== bytes[offset + j]) continue FIND_CHUNK; - return record.value; - } - return null; - } - - private store(bytes: Uint8Array, value: string): void { - const records = this.caches[bytes.length - 1]!; - const record = new CacheItem(bytes, value); - const length = records.length; - if (length >= CONST.MAX_RECORDS_PER_SIZE) records[randomU32(0, CONST.MAX_RECORDS_PER_SIZE - 1)] = record; - else records.push(record); - } - - public decode(bytes: Uint8Array, offset: number, size: number): string { - if (!size) return ''; - const cachedValue = this.get(bytes, offset, size); - if (cachedValue !== null) return cachedValue; - const value = decodeUtf8(bytes, offset, size); - // Ensure to copy a slice of bytes because the byte may be NodeJS Buffer and Buffer#slice() returns a reference to its internal ArrayBuffer. - const copy = Uint8Array.prototype.slice.call(bytes, offset, offset + size); - this.store(copy, value); - return value; - } -} +export * from '@jsonjoy.com/buffers/lib/utf8/CachedUtf8Decoder'; diff --git a/src/buffers/utf8/__tests__/encode.spec.ts b/src/buffers/utf8/__tests__/encode.spec.ts deleted file mode 100644 index 0244d3e..0000000 --- a/src/buffers/utf8/__tests__/encode.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {RandomJson} from '../../../json-random'; -import {encodeUtf8Write, encodeFrom, encodeNative, encodeTe, encode, EncodeString} from '../encode'; - -describe('can encode JavaScript string as UTF-8 string', () => { - const runEncodingTest = (name: string, encode: EncodeString) => { - test(name, () => { - const arr = new Uint8Array(50); - for (let i = 0; i < 1000; i++) { - const str = RandomJson.genString(10); - const len = encode(arr, str, str.length, str.length * 4); - const slice = arr.subarray(10, 10 + len); - const decoded = Buffer.from(slice).toString(); - expect(decoded).toBe(str); - } - }); - }; - - runEncodingTest('encodeUtf8Write', encodeUtf8Write); - runEncodingTest('encodeFrom', encodeFrom); - runEncodingTest('encodeNative', encodeNative); - runEncodingTest('encodeTe', encodeTe); - runEncodingTest('encode', encode); -}); diff --git a/src/buffers/utf8/__tests__/isUtf8.spec.ts b/src/buffers/utf8/__tests__/isUtf8.spec.ts deleted file mode 100644 index 6ea5aea..0000000 --- a/src/buffers/utf8/__tests__/isUtf8.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {isUtf8} from '../isUtf8'; - -describe('returns true for valid UTF8', () => { - const strings = [ - '', - 'hello', - 'hello world', - 'emoji: 🤔', - 'russian: Привет', - 'chinese: 你好', - 'japanese: こんにちは', - 'korean: 안녕하세요', - 'arabic: مرحبا', - 'hebrew: שלום', - 'greek: γεια σας', - 'bulgarian: Здравейте', - 'hindi: नमस्ते', - 'thai: สวัสดี', - 'special chars: !@#$%^&*()_+{}|:"<>?`-=[]\\;\',./', - ]; - for (const str of strings) { - test(str, () => { - const buf = Buffer.from(str); - expect(isUtf8(buf, 0, buf.length)).toBe(true); - }); - } -}); - -describe('returns false for non-UTF8 sequences', () => { - const strings: [name: string, Uint8Array][] = [ - ['two octets', Buffer.from([0xc3, 0x28])], - ['three octets', Buffer.from([0xe2, 0x82, 0x28])], - ['four octets', Buffer.from([0xf0, 0x90, 0x82, 0x28])], - ['five octets', Buffer.from([0xf8, 0x88, 0x82, 0x82, 0x28])], - ['six octets', Buffer.from([0xfc, 0x84, 0x82, 0x82, 0x82, 0x28])], - ]; - for (const [name, str] of strings) { - test(name, () => { - expect(isUtf8(str, 0, str.length)).toBe(false); - }); - } -}); - -describe('returns true for valid non-UTF8 sequences in the middle of buffer', () => { - const strings: [name: string, str: Uint8Array, from: number, length: number][] = [ - ['mid char valid', Buffer.from([0xc3, 0x28, 64, 0xc3, 0x28]), 2, 1], - ]; - for (const [name, str, from, length] of strings) { - test(name, () => { - expect(isUtf8(str, from, length)).toBe(true); - expect(isUtf8(str, 0, from + length)).toBe(false); - }); - } -}); diff --git a/src/buffers/utf8/decodeAscii.ts b/src/buffers/utf8/decodeAscii.ts index cb84c50..26a7b71 100644 --- a/src/buffers/utf8/decodeAscii.ts +++ b/src/buffers/utf8/decodeAscii.ts @@ -1,167 +1 @@ -const fromCharCode = String.fromCharCode; - -/** This code was borrowed form cbor-x under the MIT license. */ - -// MIT License - -// Copyright (c) 2020 Kris Zyp - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -export const decodeAscii = (src: Uint8Array, position: number, length: number): string | undefined => { - const bytes = []; - for (let i = 0; i < length; i++) { - const byte = src[position++]; - if (byte & 0x80) return; - bytes.push(byte); - } - return fromCharCode.apply(String, bytes); -}; - -export const decodeAsciiMax15 = (src: Uint8Array, position: number, length: number): string | undefined => { - if (length < 4) { - if (length < 2) { - if (length === 0) return ''; - else { - const a = src[position++]; - if ((a & 0x80) > 1) { - position -= 1; - return; - } - return fromCharCode(a); - } - } else { - const a = src[position++]; - const b = src[position++]; - if ((a & 0x80) > 0 || (b & 0x80) > 0) { - position -= 2; - return; - } - if (length < 3) return fromCharCode(a, b); - const c = src[position++]; - if ((c & 0x80) > 0) { - position -= 3; - return; - } - return fromCharCode(a, b, c); - } - } else { - const a = src[position++]; - const b = src[position++]; - const c = src[position++]; - const d = src[position++]; - if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) { - position -= 4; - return; - } - if (length < 6) { - if (length === 4) return fromCharCode(a, b, c, d); - else { - const e = src[position++]; - if ((e & 0x80) > 0) { - position -= 5; - return; - } - return fromCharCode(a, b, c, d, e); - } - } else if (length < 8) { - const e = src[position++]; - const f = src[position++]; - if ((e & 0x80) > 0 || (f & 0x80) > 0) { - position -= 6; - return; - } - if (length < 7) return fromCharCode(a, b, c, d, e, f); - const g = src[position++]; - if ((g & 0x80) > 0) { - position -= 7; - return; - } - return fromCharCode(a, b, c, d, e, f, g); - } else { - const e = src[position++]; - const f = src[position++]; - const g = src[position++]; - const h = src[position++]; - if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) { - position -= 8; - return; - } - if (length < 10) { - if (length === 8) return fromCharCode(a, b, c, d, e, f, g, h); - else { - const i = src[position++]; - if ((i & 0x80) > 0) { - position -= 9; - return; - } - return fromCharCode(a, b, c, d, e, f, g, h, i); - } - } else if (length < 12) { - const i = src[position++]; - const j = src[position++]; - if ((i & 0x80) > 0 || (j & 0x80) > 0) { - position -= 10; - return; - } - if (length < 11) return fromCharCode(a, b, c, d, e, f, g, h, i, j); - const k = src[position++]; - if ((k & 0x80) > 0) { - position -= 11; - return; - } - return fromCharCode(a, b, c, d, e, f, g, h, i, j, k); - } else { - const i = src[position++]; - const j = src[position++]; - const k = src[position++]; - const l = src[position++]; - if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) { - position -= 12; - return; - } - if (length < 14) { - if (length === 12) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l); - else { - const m = src[position++]; - if ((m & 0x80) > 0) { - position -= 13; - return; - } - return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m); - } - } else { - const m = src[position++]; - const n = src[position++]; - if ((m & 0x80) > 0 || (n & 0x80) > 0) { - position -= 14; - return; - } - if (length < 15) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n); - const o = src[position++]; - if ((o & 0x80) > 0) { - position -= 15; - return; - } - return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o); - } - } - } - } -}; +export * from '@jsonjoy.com/buffers/lib/utf8/decodeAscii'; diff --git a/src/buffers/utf8/decodeUtf8/README.md b/src/buffers/utf8/decodeUtf8/README.md deleted file mode 100644 index dbdea0c..0000000 --- a/src/buffers/utf8/decodeUtf8/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# `decodeUtf8` - -Decodes UTF-8 text from `Uint8Array` buffer to JavaScript string (UTF-16). - -- `Buffer.from(arr).subarray` (v4) is always faster than `Buffer.from(arr).slice` (v3). -- `Buffer.prototype.utf8Slice` (v2) is always faster than `Buffer.from(arr).subarray` (v4). - - But `Buffer.prototype.utf8Slice` (v2) is Node's internal implementation detail, so we need - to be able to fall back to `Buffer.from(arr).subarray` (v4). -- "JS with string concatenation" (v5) is fastest for tiny strings. -- "JS with buffering in array" (v1) is fastest for medium sized strings. -- `TextDecoder` (v6) is slower than any `Buffer` methods. - - However, `Buffer` is not available on the browser (and possibly other environments). So, - for long strings would be beneficial to use `TextDecoder` (v6). -- (v10) seems to be faster than (v5). -- (v9) seems to be the fastest out of the buffering approaches. - - -Benchmarks: - -``` -node benchmarks/util/buffers/bench.decodeUtf8.js -============================================================================= Benchmark: decodeUtf8 -Warming up each runner 1000x -------------------------------------------------------------------------- Single character, 1 bytes -👍 JS with buffering in array (v1) x 45,587,314 ops/sec ±0.35% (91 runs sampled) -👍 Buffer.prototype.utf8Slice (v2) x 9,669,294 ops/sec ±0.08% (95 runs sampled) -👍 Buffer.from(arr).slice (v3) x 6,522,838 ops/sec ±0.18% (93 runs sampled) -👍 Buffer.from(arr).subarray (v4) x 7,004,453 ops/sec ±0.17% (99 runs sampled) -👍 JS with string concatenation (v5) x 157,127,790 ops/sec ±0.08% (98 runs sampled) -👍 TextDecoder (v6) x 1,405,754 ops/sec ±1.57% (84 runs sampled) -Fastest is 👍 JS with string concatenation (v5) ----------------------------------------------------------------------------------- "Hello", 5 bytes -👍 JS with buffering in array (v1) x 26,293,330 ops/sec ±0.17% (98 runs sampled) -👍 Buffer.prototype.utf8Slice (v2) x 9,595,299 ops/sec ±0.13% (97 runs sampled) -👍 Buffer.from(arr).slice (v3) x 6,597,346 ops/sec ±0.27% (90 runs sampled) -👍 Buffer.from(arr).subarray (v4) x 6,913,974 ops/sec ±0.17% (98 runs sampled) -👍 JS with string concatenation (v5) x 33,109,095 ops/sec ±0.17% (100 runs sampled) -👍 TextDecoder (v6) x 1,393,950 ops/sec ±1.31% (87 runs sampled) -Fastest is 👍 JS with string concatenation (v5) -------------------------------------------------------------------- Short text with emoji, 14 bytes -👍 JS with buffering in array (v1) x 13,746,402 ops/sec ±0.61% (94 runs sampled) -👍 Buffer.prototype.utf8Slice (v2) x 8,573,654 ops/sec ±0.13% (99 runs sampled) -👍 Buffer.from(arr).slice (v3) x 6,003,418 ops/sec ±0.42% (97 runs sampled) -👍 Buffer.from(arr).subarray (v4) x 6,163,374 ops/sec ±0.35% (99 runs sampled) -👍 JS with string concatenation (v5) x 7,468,848 ops/sec ±0.26% (99 runs sampled) -👍 TextDecoder (v6) x 1,358,357 ops/sec ±1.32% (72 runs sampled) -Fastest is 👍 JS with buffering in array (v1) ---------------------------------------------------------------------- Repeating characters, 8 bytes -👍 JS with buffering in array (v1) x 18,606,797 ops/sec ±0.37% (99 runs sampled) -👍 Buffer.prototype.utf8Slice (v2) x 9,210,861 ops/sec ±0.26% (99 runs sampled) -👍 Buffer.from(arr).slice (v3) x 6,398,227 ops/sec ±0.23% (96 runs sampled) -👍 Buffer.from(arr).subarray (v4) x 6,820,514 ops/sec ±0.22% (99 runs sampled) -👍 JS with string concatenation (v5) x 17,943,107 ops/sec ±0.35% (94 runs sampled) -👍 TextDecoder (v6) x 1,448,300 ops/sec ±1.36% (75 runs sampled) -Fastest is 👍 JS with buffering in array (v1) --------------------------------------------------------------------- Repeating characters, 16 bytes -🤞 JS with buffering in array (v1) x 12,181,356 ops/sec ±0.26% (100 runs sampled) -🤞 Buffer.prototype.utf8Slice (v2) x 9,254,890 ops/sec ±0.25% (97 runs sampled) -🤞 Buffer.from(arr).slice (v3) x 6,407,754 ops/sec ±0.20% (99 runs sampled) -🤞 Buffer.from(arr).subarray (v4) x 6,738,914 ops/sec ±0.25% (98 runs sampled) -🤞 JS with string concatenation (v5) x 9,530,473 ops/sec ±0.21% (101 runs sampled) -🤞 TextDecoder (v6) x 1,456,139 ops/sec ±1.28% (77 runs sampled) -Fastest is 🤞 JS with buffering in array (v1) --------------------------------------------------------------------- Repeating characters, 32 bytes -🤞 JS with buffering in array (v1) x 6,327,652 ops/sec ±0.24% (100 runs sampled) -🤞 Buffer.prototype.utf8Slice (v2) x 8,958,249 ops/sec ±0.22% (99 runs sampled) -🤞 Buffer.from(arr).slice (v3) x 6,217,455 ops/sec ±0.23% (98 runs sampled) -🤞 Buffer.from(arr).subarray (v4) x 6,500,127 ops/sec ±0.18% (101 runs sampled) -🤞 JS with string concatenation (v5) x 5,647,992 ops/sec ±0.14% (99 runs sampled) -🤞 TextDecoder (v6) x 1,452,152 ops/sec ±1.26% (79 runs sampled) -Fastest is 🤞 Buffer.prototype.utf8Slice (v2) --------------------------------------------------------------------- Repeating characters, 64 bytes -🤞 JS with buffering in array (v1) x 3,141,539 ops/sec ±0.23% (99 runs sampled) -🤞 Buffer.prototype.utf8Slice (v2) x 8,898,315 ops/sec ±0.21% (99 runs sampled) -🤞 Buffer.from(arr).slice (v3) x 5,947,900 ops/sec ±0.24% (99 runs sampled) -🤞 Buffer.from(arr).subarray (v4) x 6,380,096 ops/sec ±0.17% (102 runs sampled) -🤞 JS with string concatenation (v5) x 3,027,083 ops/sec ±0.15% (96 runs sampled) -🤞 TextDecoder (v6) x 1,387,153 ops/sec ±0.86% (85 runs sampled) -Fastest is 🤞 Buffer.prototype.utf8Slice (v2) -------------------------------------------------------------------- Repeating characters, 128 bytes -🤞 JS with buffering in array (v1) x 1,525,792 ops/sec ±0.18% (98 runs sampled) -🤞 Buffer.prototype.utf8Slice (v2) x 8,600,267 ops/sec ±0.17% (98 runs sampled) -🤞 Buffer.from(arr).slice (v3) x 5,676,294 ops/sec ±0.16% (98 runs sampled) -🤞 Buffer.from(arr).subarray (v4) x 6,014,855 ops/sec ±0.23% (100 runs sampled) -🤞 JS with string concatenation (v5) x 1,612,844 ops/sec ±0.10% (100 runs sampled) -🤞 TextDecoder (v6) x 1,304,084 ops/sec ±1.14% (86 runs sampled) -Fastest is 🤞 Buffer.prototype.utf8Slice (v2) -------------------------------------------------------------------- Repeating characters, 256 bytes -🤞 JS with buffering in array (v1) x 673,037 ops/sec ±0.08% (98 runs sampled) -🤞 Buffer.prototype.utf8Slice (v2) x 7,934,918 ops/sec ±0.26% (99 runs sampled) -🤞 Buffer.from(arr).slice (v3) x 4,803,526 ops/sec ±0.25% (98 runs sampled) -🤞 Buffer.from(arr).subarray (v4) x 5,007,603 ops/sec ±0.27% (94 runs sampled) -🤞 JS with string concatenation (v5) x 816,504 ops/sec ±0.19% (101 runs sampled) -🤞 TextDecoder (v6) x 1,123,970 ops/sec ±0.90% (92 runs sampled) -Fastest is 🤞 Buffer.prototype.utf8Slice (v2) -``` diff --git a/src/buffers/utf8/decodeUtf8/__tests__/decodeUtf8.spec.ts b/src/buffers/utf8/decodeUtf8/__tests__/decodeUtf8.spec.ts deleted file mode 100644 index 2a74db7..0000000 --- a/src/buffers/utf8/decodeUtf8/__tests__/decodeUtf8.spec.ts +++ /dev/null @@ -1,120 +0,0 @@ -import v1 from '../v1'; -import v2 from '../v2'; -import v3 from '../v3'; -import v4 from '../v4'; -import v5 from '../v5'; -import v6 from '../v6'; -import v7 from '../v7'; -import v8 from '../v8'; -import v9 from '../v9'; -import v10 from '../v10'; -import v11 from '../v11'; -import v12 from '../v12'; -import v13 from '../v13'; -import v14 from '../v14'; -import v15 from '../v15'; -import v16 from '../v16'; -import v17 from '../v17'; -import v18 from '../v18'; -import v19 from '../v19'; - -type Decoder = (buf: Uint8Array, start: number, length: number) => string; - -const runTests = (name: string, decodeUtf8: Decoder) => { - describe(name, () => { - test('can decode basic string', () => { - const arr = new ArrayBuffer(8); - const buf = new Uint8Array(arr); - buf[0] = 1; - buf[1] = 0x61; - buf[2] = 0x62; - buf[3] = 0x63; - buf[4] = 0x64; - buf[5] = 0x65; - buf[6] = 0x66; - buf[7] = 1; - const uint8 = buf.subarray(1, 7); - const str = decodeUtf8(uint8, 1, 2); - expect(str).toBe('bc'); - }); - - test('can decode emoji', () => { - const arr = new ArrayBuffer(8); - const buf = new Uint8Array(arr); - buf[0] = 1; - buf[1] = 0x61; - buf[2] = 0xf0; - buf[3] = 0x9f; - buf[4] = 0x98; - buf[5] = 0x80; - buf[6] = 0x62; - buf[7] = 1; - const uint8 = buf.subarray(1, 7); - const str = decodeUtf8(uint8, 1, 4); - expect(str).toBe('😀'); - }); - - test('can decode a long string', () => { - const str = 'a'.repeat(33333); - const buf = Buffer.from(str); - const arr = new Uint8Array(buf.length); - for (let i = 0; i < arr.length; i++) arr[i] = buf[i]; - const str2 = decodeUtf8(arr, 0, arr.length); - expect(str2).toBe(str); - }); - - test('can decode real-world sentence', () => { - const str = '💯 RëactQuill v2 ReactQuill 2 is here, baby! And it brings a füll port to TypeScript and React 16+.'; - const buf = Buffer.from(str); - const arr = new Uint8Array(buf.length); - for (let i = 0; i < arr.length; i++) arr[i] = buf[i]; - const str2 = decodeUtf8(arr, 0, arr.length); - expect(str2).toBe(str); - }); - - test('can decode various types of characters', () => { - const alphabet = [ - // 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', - // 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - // 'u', 'v', 'w', 'x', 'y', 'z', - // 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - // 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - // 'U', 'V', 'W', 'X', 'Y', 'Z', - // '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - // '-', '_', '.', ',', ';', '!', '@', '#', '$', '%', - // '^', '&', '*', '\\', '/', '(', ')', '+', '=', '\n', - // '👍', '🏻', '😛', 'ä', 'ö', 'ü', 'ß', 'а', 'б', 'в', - 'г', - '诶', - '必', - '西', - ]; - const str = alphabet.join(''); - const buf = Buffer.from(str); - const arr = new Uint8Array(buf.length); - for (let i = 0; i < arr.length; i++) arr[i] = buf[i]; - const str2 = decodeUtf8(arr, 0, arr.length); - expect(str2).toBe(str); - }); - }); -}; - -runTests('v1', v1); -runTests('v2', v2); -runTests('v3', v3); -runTests('v4', v4); -runTests('v5', v5); -runTests('v6', v6); -runTests('v7', v7); -runTests('v8', v8); -runTests('v9', v9); -runTests('v10', v10); -runTests('v11', v11); -runTests('v12', v12); -runTests('v13', v13); -runTests('v14', v14); -runTests('v15', v15); -runTests('v16', v16); -runTests('v17', v17); -runTests('v18', v18); -runTests('v19', v19); diff --git a/src/buffers/utf8/decodeUtf8/index.ts b/src/buffers/utf8/decodeUtf8/index.ts index 7e93498..0261277 100644 --- a/src/buffers/utf8/decodeUtf8/index.ts +++ b/src/buffers/utf8/decodeUtf8/index.ts @@ -1,3 +1 @@ -import decodeUtf8 from './v16'; - -export {decodeUtf8}; +export * from '@jsonjoy.com/buffers/lib/utf8/decodeUtf8'; diff --git a/src/buffers/utf8/decodeUtf8/v1.ts b/src/buffers/utf8/decodeUtf8/v1.ts deleted file mode 100644 index d912302..0000000 --- a/src/buffers/utf8/decodeUtf8/v1.ts +++ /dev/null @@ -1,38 +0,0 @@ -export default (buf: Uint8Array, start: number, length: number): string => { - let offset = start; - const end = offset + length; - const units: Array = []; - let result = ''; - while (offset < end) { - const byte1 = buf[offset++]!; - if ((byte1 & 0x80) === 0) { - units.push(byte1); - } else if ((byte1 & 0xe0) === 0xc0) { - const byte2 = buf[offset++]! & 0x3f; - units.push(((byte1 & 0x1f) << 6) | byte2); - } else if ((byte1 & 0xf0) === 0xe0) { - const byte2 = buf[offset++]! & 0x3f; - const byte3 = buf[offset++]! & 0x3f; - units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3); - } else if ((byte1 & 0xf8) === 0xf0) { - const byte2 = buf[offset++]! & 0x3f; - const byte3 = buf[offset++]! & 0x3f; - const byte4 = buf[offset++]! & 0x3f; - let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4; - if (unit > 0xffff) { - unit -= 0x10000; - units.push(((unit >>> 10) & 0x3ff) | 0xd800); - unit = 0xdc00 | (unit & 0x3ff); - } - units.push(unit); - } else { - units.push(byte1); - } - if (units.length >= 1000) { - result += String.fromCharCode(...units); - units.length = 0; - } - } - if (units.length > 0) result += String.fromCharCode(...units); - return result; -}; diff --git a/src/buffers/utf8/decodeUtf8/v10.ts b/src/buffers/utf8/decodeUtf8/v10.ts deleted file mode 100644 index ebf8b03..0000000 --- a/src/buffers/utf8/decodeUtf8/v10.ts +++ /dev/null @@ -1,39 +0,0 @@ -const fromCharCode = String.fromCharCode; - -export default (buf: Uint8Array, start: number, length: number): string => { - let offset = start; - const end = offset + length; - let str = ''; - while (offset < end) { - const octet1 = buf[offset++]!; - if ((octet1 & 0x80) === 0) { - str += fromCharCode(octet1); - continue; - } - const octet2 = buf[offset++]! & 0x3f; - if ((octet1 & 0xe0) === 0xc0) { - str += fromCharCode(((octet1 & 0x1f) << 6) | octet2); - continue; - } - const octet3 = buf[offset++]! & 0x3f; - if ((octet1 & 0xf0) === 0xe0) { - str += fromCharCode(((octet1 & 0x1f) << 12) | (octet2 << 6) | octet3); - continue; - } - if ((octet1 & 0xf8) === 0xf0) { - const octet4 = buf[offset++]! & 0x3f; - let unit = ((octet1 & 0x07) << 0x12) | (octet2 << 0x0c) | (octet3 << 0x06) | octet4; - if (unit > 0xffff) { - unit -= 0x10000; - const unit0 = ((unit >>> 10) & 0x3ff) | 0xd800; - unit = 0xdc00 | (unit & 0x3ff); - str += fromCharCode(unit0, unit); - } else { - str += fromCharCode(unit); - } - } else { - str += fromCharCode(octet1); - } - } - return str; -}; diff --git a/src/buffers/utf8/decodeUtf8/v11.ts b/src/buffers/utf8/decodeUtf8/v11.ts deleted file mode 100644 index f3beea7..0000000 --- a/src/buffers/utf8/decodeUtf8/v11.ts +++ /dev/null @@ -1,2 +0,0 @@ -const utf8Slice = Buffer.prototype.utf8Slice; -export default (buf: Uint8Array, start: number, length: number): string => utf8Slice.call(buf, start, start + length); diff --git a/src/buffers/utf8/decodeUtf8/v12.ts b/src/buffers/utf8/decodeUtf8/v12.ts deleted file mode 100644 index 4a31f6d..0000000 --- a/src/buffers/utf8/decodeUtf8/v12.ts +++ /dev/null @@ -1,5 +0,0 @@ -const from = Buffer.from; -export default (arr: Uint8Array, start: number, length: number): string => - from(arr) - .subarray(start, start + length) - .toString(); diff --git a/src/buffers/utf8/decodeUtf8/v13.ts b/src/buffers/utf8/decodeUtf8/v13.ts deleted file mode 100644 index de26b1d..0000000 --- a/src/buffers/utf8/decodeUtf8/v13.ts +++ /dev/null @@ -1,27 +0,0 @@ -import v10 from './v10'; - -let decode = v10; - -const hasBuffer = typeof Buffer !== 'undefined'; -const utf8Slice = hasBuffer ? Buffer.prototype.utf8Slice : null; - -if (utf8Slice) { - decode = (buf: Uint8Array, start: number, length: number): string => - length <= 10 ? v10(buf, start, length) : utf8Slice.call(buf, start, start + length); -} else { - const from = hasBuffer ? Buffer.from : null; - if (from) { - decode = (buf: Uint8Array, start: number, length: number): string => - length < 30 - ? v10(buf, start, length) - : from(buf) - .subarray(start, start + length) - .toString(); - } else if (typeof TextDecoder !== 'undefined') { - const decoder = new TextDecoder(); - decode = (buf: Uint8Array, start: number, length: number): string => - length < 150 ? v10(buf, start, length) : decoder.decode(buf.subarray(start, start + length)); - } -} - -export default decode; diff --git a/src/buffers/utf8/decodeUtf8/v14.ts b/src/buffers/utf8/decodeUtf8/v14.ts deleted file mode 100644 index d9062ad..0000000 --- a/src/buffers/utf8/decodeUtf8/v14.ts +++ /dev/null @@ -1,8 +0,0 @@ -import v10 from './v10'; - -const hasBuffer = typeof Buffer !== 'undefined'; -const utf8Slice = hasBuffer ? Buffer.prototype.utf8Slice : null; - -export default utf8Slice - ? (buf: Uint8Array, start: number, length: number): string => utf8Slice.call(buf, start, start + length) - : v10; diff --git a/src/buffers/utf8/decodeUtf8/v15.ts b/src/buffers/utf8/decodeUtf8/v15.ts deleted file mode 100644 index b5557c0..0000000 --- a/src/buffers/utf8/decodeUtf8/v15.ts +++ /dev/null @@ -1,16 +0,0 @@ -import v10 from './v10'; - -const hasBuffer = typeof Buffer !== 'undefined'; -const utf8Slice = hasBuffer ? Buffer.prototype.utf8Slice : null; -const from = hasBuffer ? Buffer.from : null; - -export default (buf: Uint8Array, start: number, length: number): string => { - const end = start + length; - return length > 8 - ? utf8Slice - ? utf8Slice.call(buf, start, end) - : from - ? from(buf).subarray(start, end).toString('utf8') - : v10(buf, start, length) - : v10(buf, start, length); -}; diff --git a/src/buffers/utf8/decodeUtf8/v16.ts b/src/buffers/utf8/decodeUtf8/v16.ts deleted file mode 100644 index c95f699..0000000 --- a/src/buffers/utf8/decodeUtf8/v16.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {decodeAscii, decodeAsciiMax15} from '../decodeAscii'; -import v18 from './v18'; - -type Decoder = (buf: Uint8Array, start: number, length: number) => string; - -const hasBuffer = typeof Buffer !== 'undefined'; -const utf8Slice = hasBuffer ? Buffer.prototype.utf8Slice : null; -const from = hasBuffer ? Buffer.from : null; - -const shortDecoder: Decoder = (buf, start, length) => decodeAsciiMax15(buf, start, length) ?? v18(buf, start, length); - -const midDecoder: Decoder = (buf, start, length) => decodeAscii(buf, start, length) ?? v18(buf, start, length); - -const longDecoder: Decoder = utf8Slice - ? (buf, start, length) => utf8Slice.call(buf, start, start + length) - : from - ? (buf, start, length) => - from(buf) - .subarray(start, start + length) - .toString('utf8') - : v18; - -const decoder: Decoder = (buf, start, length): string => { - if (length < 16) return shortDecoder(buf, start, length); - if (length < 32) return midDecoder(buf, start, length); - return longDecoder(buf, start, length); -}; - -export default decoder; diff --git a/src/buffers/utf8/decodeUtf8/v17.ts b/src/buffers/utf8/decodeUtf8/v17.ts deleted file mode 100644 index 604ac7e..0000000 --- a/src/buffers/utf8/decodeUtf8/v17.ts +++ /dev/null @@ -1,37 +0,0 @@ -const fromCharCode = String.fromCharCode; - -export default (buf: Uint8Array, start: number, length: number): string => { - let offset = start; - const end = offset + length; - let str = ''; - while (offset < end) { - let code = buf[offset++]!; - if ((code & 0x80) !== 0) { - const octet2 = buf[offset++]! & 0x3f; - if ((code & 0xe0) === 0xc0) { - code = ((code & 0x1f) << 6) | octet2; - } else { - const octet3 = buf[offset++]! & 0x3f; - if ((code & 0xf0) === 0xe0) { - code = ((code & 0x1f) << 12) | (octet2 << 6) | octet3; - } else { - if ((code & 0xf8) === 0xf0) { - const octet4 = buf[offset++]! & 0x3f; - let unit = ((code & 0x07) << 0x12) | (octet2 << 0x0c) | (octet3 << 0x06) | octet4; - if (unit > 0xffff) { - unit -= 0x10000; - const unit0 = ((unit >>> 10) & 0x3ff) | 0xd800; - unit = 0xdc00 | (unit & 0x3ff); - str += fromCharCode(unit0); - code = unit; - } else { - code = unit; - } - } - } - } - } - str += fromCharCode(code); - } - return str; -}; diff --git a/src/buffers/utf8/decodeUtf8/v18.ts b/src/buffers/utf8/decodeUtf8/v18.ts deleted file mode 100644 index e1b518e..0000000 --- a/src/buffers/utf8/decodeUtf8/v18.ts +++ /dev/null @@ -1,36 +0,0 @@ -const fromCharCode = String.fromCharCode; - -export default (buf: Uint8Array, start: number, length: number): string => { - let offset = start; - const end = offset + length; - const points: number[] = []; - while (offset < end) { - let code = buf[offset++]!; - if ((code & 0x80) !== 0) { - const octet2 = buf[offset++]! & 0x3f; - if ((code & 0xe0) === 0xc0) { - code = ((code & 0x1f) << 6) | octet2; - } else { - const octet3 = buf[offset++]! & 0x3f; - if ((code & 0xf0) === 0xe0) { - code = ((code & 0x1f) << 12) | (octet2 << 6) | octet3; - } else { - if ((code & 0xf8) === 0xf0) { - const octet4 = buf[offset++]! & 0x3f; - let unit = ((code & 0x07) << 0x12) | (octet2 << 0x0c) | (octet3 << 0x06) | octet4; - if (unit > 0xffff) { - unit -= 0x10000; - const unit0 = ((unit >>> 10) & 0x3ff) | 0xd800; - code = 0xdc00 | (unit & 0x3ff); - points.push(unit0); - } else { - code = unit; - } - } - } - } - } - points.push(code); - } - return fromCharCode.apply(String, points); -}; diff --git a/src/buffers/utf8/decodeUtf8/v19.ts b/src/buffers/utf8/decodeUtf8/v19.ts deleted file mode 100644 index 2bbc39d..0000000 --- a/src/buffers/utf8/decodeUtf8/v19.ts +++ /dev/null @@ -1,3 +0,0 @@ -const {readUtf8} = require('json-pack-napi'); - -export default readUtf8; diff --git a/src/buffers/utf8/decodeUtf8/v2.ts b/src/buffers/utf8/decodeUtf8/v2.ts deleted file mode 100644 index c661240..0000000 --- a/src/buffers/utf8/decodeUtf8/v2.ts +++ /dev/null @@ -1,2 +0,0 @@ -export default (buf: Uint8Array, start: number, length: number): string => - Buffer.prototype.utf8Slice.call(buf, start, start + length); diff --git a/src/buffers/utf8/decodeUtf8/v3.ts b/src/buffers/utf8/decodeUtf8/v3.ts deleted file mode 100644 index fb8adea..0000000 --- a/src/buffers/utf8/decodeUtf8/v3.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default (arr: Uint8Array, start: number, length: number): string => - Buffer.from(arr) - .slice(start, start + length) - .toString(); diff --git a/src/buffers/utf8/decodeUtf8/v4.ts b/src/buffers/utf8/decodeUtf8/v4.ts deleted file mode 100644 index 23b2678..0000000 --- a/src/buffers/utf8/decodeUtf8/v4.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default (arr: Uint8Array, start: number, length: number): string => - Buffer.from(arr) - .subarray(start, start + length) - .toString(); diff --git a/src/buffers/utf8/decodeUtf8/v5.ts b/src/buffers/utf8/decodeUtf8/v5.ts deleted file mode 100644 index c00b6ca..0000000 --- a/src/buffers/utf8/decodeUtf8/v5.ts +++ /dev/null @@ -1,30 +0,0 @@ -export default (uint8: Uint8Array, start: number, length: number): string => { - const end = start + length; - let x = start; - let str = ''; - while (x < end) { - const b1 = uint8[x++]!; - if ((b1 & 0x80) === 0) { - str += String.fromCharCode(b1); - continue; - } else if ((b1 & 0xe0) === 0xc0) { - str += String.fromCharCode(((b1 & 0x1f) << 6) | (uint8[x++]! & 0x3f)); - } else if ((b1 & 0xf0) === 0xe0) { - str += String.fromCharCode(((b1 & 0x1f) << 12) | ((uint8[x++]! & 0x3f) << 6) | (uint8[x++]! & 0x3f)); - } else if ((b1 & 0xf8) === 0xf0) { - const b2 = uint8[x++]! & 0x3f; - const b3 = uint8[x++]! & 0x3f; - const b4 = uint8[x++]! & 0x3f; - let code = ((b1 & 0x07) << 0x12) | (b2 << 0x0c) | (b3 << 0x06) | b4; - if (code > 0xffff) { - code -= 0x10000; - str += String.fromCharCode(((code >>> 10) & 0x3ff) | 0xd800); - code = 0xdc00 | (code & 0x3ff); - } - str += String.fromCharCode(code); - } else { - str += String.fromCharCode(b1); - } - } - return str; -}; diff --git a/src/buffers/utf8/decodeUtf8/v6.ts b/src/buffers/utf8/decodeUtf8/v6.ts deleted file mode 100644 index 88acf9b..0000000 --- a/src/buffers/utf8/decodeUtf8/v6.ts +++ /dev/null @@ -1,6 +0,0 @@ -const sharedTextDecoder = new TextDecoder(); - -export default (uint8: Uint8Array, start: number, length: number): string => { - const stringBytes = uint8.subarray(start, start + length); - return sharedTextDecoder.decode(stringBytes); -}; diff --git a/src/buffers/utf8/decodeUtf8/v7.ts b/src/buffers/utf8/decodeUtf8/v7.ts deleted file mode 100644 index 32ff0da..0000000 --- a/src/buffers/utf8/decodeUtf8/v7.ts +++ /dev/null @@ -1,32 +0,0 @@ -export default (buf: Uint8Array, start: number, length: number): string => { - let offset = start; - const end = offset + length; - const codes: Array = []; - while (offset < end) { - const octet1 = buf[offset++]!; - if ((octet1 & 0x80) === 0) { - codes.push(octet1); - } else if ((octet1 & 0xe0) === 0xc0) { - const octet2 = buf[offset++]! & 0x3f; - codes.push(((octet1 & 0x1f) << 6) | octet2); - } else if ((octet1 & 0xf0) === 0xe0) { - const octet2 = buf[offset++]! & 0x3f; - const octet3 = buf[offset++]! & 0x3f; - codes.push(((octet1 & 0x1f) << 12) | (octet2 << 6) | octet3); - } else if ((octet1 & 0xf8) === 0xf0) { - const octet2 = buf[offset++]! & 0x3f; - const octet3 = buf[offset++]! & 0x3f; - const octet4 = buf[offset++]! & 0x3f; - let unit = ((octet1 & 0x07) << 0x12) | (octet2 << 0x0c) | (octet3 << 0x06) | octet4; - if (unit > 0xffff) { - unit -= 0x10000; - codes.push(((unit >>> 10) & 0x3ff) | 0xd800); - unit = 0xdc00 | (unit & 0x3ff); - } - codes.push(unit); - } else { - codes.push(octet1); - } - } - return String.fromCharCode(...codes); -}; diff --git a/src/buffers/utf8/decodeUtf8/v8.ts b/src/buffers/utf8/decodeUtf8/v8.ts deleted file mode 100644 index 28b77f6..0000000 --- a/src/buffers/utf8/decodeUtf8/v8.ts +++ /dev/null @@ -1,39 +0,0 @@ -// String.fromCharCode(...units) flushing happens more often. -export default (buf: Uint8Array, start: number, length: number): string => { - let offset = start; - const end = offset + length; - const units: Array = []; - let result = ''; - while (offset < end) { - const byte1 = buf[offset++]!; - if ((byte1 & 0x80) === 0) { - units.push(byte1); - } else if ((byte1 & 0xe0) === 0xc0) { - const byte2 = buf[offset++]! & 0x3f; - units.push(((byte1 & 0x1f) << 6) | byte2); - } else if ((byte1 & 0xf0) === 0xe0) { - const byte2 = buf[offset++]! & 0x3f; - const byte3 = buf[offset++]! & 0x3f; - units.push(((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3); - } else if ((byte1 & 0xf8) === 0xf0) { - const byte2 = buf[offset++]! & 0x3f; - const byte3 = buf[offset++]! & 0x3f; - const byte4 = buf[offset++]! & 0x3f; - let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4; - if (unit > 0xffff) { - unit -= 0x10000; - units.push(((unit >>> 10) & 0x3ff) | 0xd800); - unit = 0xdc00 | (unit & 0x3ff); - } - units.push(unit); - } else { - units.push(byte1); - } - if (units.length >= 8) { - result += String.fromCharCode(...units); - units.length = 0; - } - } - if (units.length > 0) result += String.fromCharCode(...units); - return result; -}; diff --git a/src/buffers/utf8/decodeUtf8/v9.ts b/src/buffers/utf8/decodeUtf8/v9.ts deleted file mode 100644 index 975c03e..0000000 --- a/src/buffers/utf8/decodeUtf8/v9.ts +++ /dev/null @@ -1,35 +0,0 @@ -export default (buf: Uint8Array, start: number, length: number): string => { - let offset = start; - const end = offset + length; - const codes: Array = []; - while (offset < end) { - const octet1 = buf[offset++]!; - if ((octet1 & 0x80) === 0) { - codes.push(octet1); - continue; - } - const octet2 = buf[offset++]! & 0x3f; - if ((octet1 & 0xe0) === 0xc0) { - codes.push(((octet1 & 0x1f) << 6) | octet2); - continue; - } - const octet3 = buf[offset++]! & 0x3f; - if ((octet1 & 0xf0) === 0xe0) { - codes.push(((octet1 & 0x1f) << 12) | (octet2 << 6) | octet3); - continue; - } - if ((octet1 & 0xf8) === 0xf0) { - const octet4 = buf[offset++]! & 0x3f; - let unit = ((octet1 & 0x07) << 0x12) | (octet2 << 0x0c) | (octet3 << 0x06) | octet4; - if (unit > 0xffff) { - unit -= 0x10000; - codes.push(((unit >>> 10) & 0x3ff) | 0xd800); - unit = 0xdc00 | (unit & 0x3ff); - } - codes.push(unit); - } else { - codes.push(octet1); - } - } - return String.fromCharCode(...codes); -}; diff --git a/src/buffers/utf8/encode.ts b/src/buffers/utf8/encode.ts index dedb236..1d50b2c 100644 --- a/src/buffers/utf8/encode.ts +++ b/src/buffers/utf8/encode.ts @@ -1,58 +1 @@ -const hasBuffer = typeof Buffer !== undefined; -const utf8Write = hasBuffer - ? (Buffer.prototype.utf8Write as (this: Uint8Array, str: string, pos: number, maxLength: number) => number) - : null; -const from = hasBuffer ? Buffer.from : null; - -export type EncodeString = (arr: Uint8Array, str: string, pos: number, maxLength: number) => number; - -export const encodeUtf8Write: EncodeString = (arr: Uint8Array, str: string, pos: number, maxLength: number): number => - utf8Write!.call(arr, str, pos, maxLength); - -export const encodeFrom: EncodeString = (arr: Uint8Array, str: string, pos: number, maxLength: number): number => { - const offset = arr.byteOffset + pos; - const buf = from!(arr.buffer).subarray(offset, offset + maxLength); - return buf.write(str, 0, maxLength, 'utf8'); -}; - -export const encodeNative: EncodeString = (arr: Uint8Array, str: string, pos: number, maxLength: number): number => { - const length = str.length; - const start = pos; - let curr = 0; - while (curr < length) { - let value = str.charCodeAt(curr++); - if ((value & 0xffffff80) === 0) { - arr[pos++] = value; - continue; - } else if ((value & 0xfffff800) === 0) { - arr[pos++] = ((value >> 6) & 0x1f) | 0xc0; - } else { - if (value >= 0xd800 && value <= 0xdbff) { - if (curr < length) { - const extra = str.charCodeAt(curr); - if ((extra & 0xfc00) === 0xdc00) { - curr++; - value = ((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000; - } - } - } - if ((value & 0xffff0000) === 0) { - arr[pos++] = ((value >> 12) & 0x0f) | 0xe0; - arr[pos++] = ((value >> 6) & 0x3f) | 0x80; - } else { - arr[pos++] = ((value >> 18) & 0x07) | 0xf0; - arr[pos++] = ((value >> 12) & 0x3f) | 0x80; - arr[pos++] = ((value >> 6) & 0x3f) | 0x80; - } - } - arr[pos++] = (value & 0x3f) | 0x80; - } - return pos - start; -}; - -const textEncoder = typeof TextEncoder !== 'undefined' ? new TextEncoder() : null; - -export const encodeTe: EncodeString = (arr: Uint8Array, str: string, pos: number, maxLength: number): number => - textEncoder!.encodeInto(str, arr.subarray(pos, pos + maxLength)).written!; - -export const encode = utf8Write ? encodeUtf8Write : from ? encodeFrom : encodeNative; +export * from '@jsonjoy.com/buffers/lib/utf8/encode'; diff --git a/src/buffers/utf8/isUtf8.ts b/src/buffers/utf8/isUtf8.ts index 5b846f0..3b6e5f9 100644 --- a/src/buffers/utf8/isUtf8.ts +++ b/src/buffers/utf8/isUtf8.ts @@ -1,8 +1,4 @@ -/** - * Validates that the given data is valid UTF-8 text. - * @param buf Data to check. - * @returns True if the data is valid UTF-8. - */ +export * from '@jsonjoy.com/buffers/lib/utf8/isUtf8'; export const isUtf8 = (buf: Uint8Array, from: number, length: number): boolean => { const to = from + length; while (from < to) { diff --git a/src/buffers/utf8/sharedCachedUtf8Decoder.ts b/src/buffers/utf8/sharedCachedUtf8Decoder.ts index 86b2d4a..c819be3 100644 --- a/src/buffers/utf8/sharedCachedUtf8Decoder.ts +++ b/src/buffers/utf8/sharedCachedUtf8Decoder.ts @@ -1,3 +1 @@ -import {CachedUtf8Decoder} from './CachedUtf8Decoder'; - -export default new CachedUtf8Decoder(); +export * from '@jsonjoy.com/buffers/lib/utf8/sharedCachedUtf8Decoder'; diff --git a/yarn.lock b/yarn.lock index 98cc922..c71bd90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -565,6 +565,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsonjoy.com/buffers@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz#ade6895b7d3883d70f87b5743efaa12c71dfef7a" + integrity sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -2331,7 +2336,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2349,7 +2363,14 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2602,7 +2623,16 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==