319 lines
7.6 KiB
JavaScript
319 lines
7.6 KiB
JavaScript
|
// https://deno.land/std@0.185.0/bytes/bytes_list.ts
|
||
|
var BytesList = class {
|
||
|
#len = 0;
|
||
|
#chunks = [];
|
||
|
constructor() {
|
||
|
}
|
||
|
/**
|
||
|
* Total size of bytes
|
||
|
*/
|
||
|
size() {
|
||
|
return this.#len;
|
||
|
}
|
||
|
/**
|
||
|
* Push bytes with given offset infos
|
||
|
*/
|
||
|
add(value, start = 0, end = value.byteLength) {
|
||
|
if (value.byteLength === 0 || end - start === 0) {
|
||
|
return;
|
||
|
}
|
||
|
checkRange(start, end, value.byteLength);
|
||
|
this.#chunks.push({
|
||
|
value,
|
||
|
end,
|
||
|
start,
|
||
|
offset: this.#len
|
||
|
});
|
||
|
this.#len += end - start;
|
||
|
}
|
||
|
/**
|
||
|
* Drop head `n` bytes.
|
||
|
*/
|
||
|
shift(n) {
|
||
|
if (n === 0) {
|
||
|
return;
|
||
|
}
|
||
|
if (this.#len <= n) {
|
||
|
this.#chunks = [];
|
||
|
this.#len = 0;
|
||
|
return;
|
||
|
}
|
||
|
const idx = this.getChunkIndex(n);
|
||
|
this.#chunks.splice(0, idx);
|
||
|
const [chunk] = this.#chunks;
|
||
|
if (chunk) {
|
||
|
const diff = n - chunk.offset;
|
||
|
chunk.start += diff;
|
||
|
}
|
||
|
let offset = 0;
|
||
|
for (const chunk2 of this.#chunks) {
|
||
|
chunk2.offset = offset;
|
||
|
offset += chunk2.end - chunk2.start;
|
||
|
}
|
||
|
this.#len = offset;
|
||
|
}
|
||
|
/**
|
||
|
* Find chunk index in which `pos` locates by binary-search
|
||
|
* returns -1 if out of range
|
||
|
*/
|
||
|
getChunkIndex(pos) {
|
||
|
let max = this.#chunks.length;
|
||
|
let min = 0;
|
||
|
while (true) {
|
||
|
const i = min + Math.floor((max - min) / 2);
|
||
|
if (i < 0 || this.#chunks.length <= i) {
|
||
|
return -1;
|
||
|
}
|
||
|
const { offset, start, end } = this.#chunks[i];
|
||
|
const len = end - start;
|
||
|
if (offset <= pos && pos < offset + len) {
|
||
|
return i;
|
||
|
} else if (offset + len <= pos) {
|
||
|
min = i + 1;
|
||
|
} else {
|
||
|
max = i - 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Get indexed byte from chunks
|
||
|
*/
|
||
|
get(i) {
|
||
|
if (i < 0 || this.#len <= i) {
|
||
|
throw new Error("out of range");
|
||
|
}
|
||
|
const idx = this.getChunkIndex(i);
|
||
|
const { value, offset, start } = this.#chunks[idx];
|
||
|
return value[start + i - offset];
|
||
|
}
|
||
|
/**
|
||
|
* Iterator of bytes from given position
|
||
|
*/
|
||
|
*iterator(start = 0) {
|
||
|
const startIdx = this.getChunkIndex(start);
|
||
|
if (startIdx < 0)
|
||
|
return;
|
||
|
const first = this.#chunks[startIdx];
|
||
|
let firstOffset = start - first.offset;
|
||
|
for (let i = startIdx; i < this.#chunks.length; i++) {
|
||
|
const chunk = this.#chunks[i];
|
||
|
for (let j = chunk.start + firstOffset; j < chunk.end; j++) {
|
||
|
yield chunk.value[j];
|
||
|
}
|
||
|
firstOffset = 0;
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Returns subset of bytes copied
|
||
|
*/
|
||
|
slice(start, end = this.#len) {
|
||
|
if (end === start) {
|
||
|
return new Uint8Array();
|
||
|
}
|
||
|
checkRange(start, end, this.#len);
|
||
|
const result = new Uint8Array(end - start);
|
||
|
const startIdx = this.getChunkIndex(start);
|
||
|
const endIdx = this.getChunkIndex(end - 1);
|
||
|
let written = 0;
|
||
|
for (let i = startIdx; i <= endIdx; i++) {
|
||
|
const {
|
||
|
value: chunkValue,
|
||
|
start: chunkStart,
|
||
|
end: chunkEnd,
|
||
|
offset: chunkOffset
|
||
|
} = this.#chunks[i];
|
||
|
const readStart = chunkStart + (i === startIdx ? start - chunkOffset : 0);
|
||
|
const readEnd = i === endIdx ? end - chunkOffset + chunkStart : chunkEnd;
|
||
|
const len = readEnd - readStart;
|
||
|
result.set(chunkValue.subarray(readStart, readEnd), written);
|
||
|
written += len;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
/**
|
||
|
* Concatenate chunks into single Uint8Array copied.
|
||
|
*/
|
||
|
concat() {
|
||
|
const result = new Uint8Array(this.#len);
|
||
|
let sum = 0;
|
||
|
for (const { value, start, end } of this.#chunks) {
|
||
|
result.set(value.subarray(start, end), sum);
|
||
|
sum += end - start;
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
};
|
||
|
function checkRange(start, end, len) {
|
||
|
if (start < 0 || len < start || end < 0 || len < end || end < start) {
|
||
|
throw new Error("invalid range");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/concat.ts
|
||
|
function concat(...buf) {
|
||
|
let length = 0;
|
||
|
for (const b of buf) {
|
||
|
length += b.length;
|
||
|
}
|
||
|
const output = new Uint8Array(length);
|
||
|
let index = 0;
|
||
|
for (const b of buf) {
|
||
|
output.set(b, index);
|
||
|
index += b.length;
|
||
|
}
|
||
|
return output;
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/copy.ts
|
||
|
function copy(src, dst, off = 0) {
|
||
|
off = Math.max(0, Math.min(off, dst.byteLength));
|
||
|
const dstBytesAvailable = dst.byteLength - off;
|
||
|
if (src.byteLength > dstBytesAvailable) {
|
||
|
src = src.subarray(0, dstBytesAvailable);
|
||
|
}
|
||
|
dst.set(src, off);
|
||
|
return src.byteLength;
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/ends_with.ts
|
||
|
function endsWith(source, suffix) {
|
||
|
for (let srci = source.length - 1, sfxi = suffix.length - 1; sfxi >= 0; srci--, sfxi--) {
|
||
|
if (source[srci] !== suffix[sfxi])
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/equals.ts
|
||
|
function equalsNaive(a, b) {
|
||
|
for (let i = 0; i < b.length; i++) {
|
||
|
if (a[i] !== b[i])
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
function equals32Bit(a, b) {
|
||
|
const len = a.length;
|
||
|
const compressable = Math.floor(len / 4);
|
||
|
const compressedA = new Uint32Array(a.buffer, 0, compressable);
|
||
|
const compressedB = new Uint32Array(b.buffer, 0, compressable);
|
||
|
for (let i = compressable * 4; i < len; i++) {
|
||
|
if (a[i] !== b[i])
|
||
|
return false;
|
||
|
}
|
||
|
for (let i = 0; i < compressedA.length; i++) {
|
||
|
if (compressedA[i] !== compressedB[i])
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
function equals(a, b) {
|
||
|
if (a.length !== b.length) {
|
||
|
return false;
|
||
|
}
|
||
|
return a.length < 1e3 ? equalsNaive(a, b) : equals32Bit(a, b);
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/index_of_needle.ts
|
||
|
function indexOfNeedle(source, needle, start = 0) {
|
||
|
if (start >= source.length) {
|
||
|
return -1;
|
||
|
}
|
||
|
if (start < 0) {
|
||
|
start = Math.max(0, source.length + start);
|
||
|
}
|
||
|
const s = needle[0];
|
||
|
for (let i = start; i < source.length; i++) {
|
||
|
if (source[i] !== s)
|
||
|
continue;
|
||
|
const pin = i;
|
||
|
let matched = 1;
|
||
|
let j = i;
|
||
|
while (matched < needle.length) {
|
||
|
j++;
|
||
|
if (source[j] !== needle[j - pin]) {
|
||
|
break;
|
||
|
}
|
||
|
matched++;
|
||
|
}
|
||
|
if (matched === needle.length) {
|
||
|
return pin;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/includes_needle.ts
|
||
|
function includesNeedle(source, needle, start = 0) {
|
||
|
return indexOfNeedle(source, needle, start) !== -1;
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/last_index_of_needle.ts
|
||
|
function lastIndexOfNeedle(source, needle, start = source.length - 1) {
|
||
|
if (start < 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
if (start >= source.length) {
|
||
|
start = source.length - 1;
|
||
|
}
|
||
|
const e = needle[needle.length - 1];
|
||
|
for (let i = start; i >= 0; i--) {
|
||
|
if (source[i] !== e)
|
||
|
continue;
|
||
|
const pin = i;
|
||
|
let matched = 1;
|
||
|
let j = i;
|
||
|
while (matched < needle.length) {
|
||
|
j--;
|
||
|
if (source[j] !== needle[needle.length - 1 - (pin - j)]) {
|
||
|
break;
|
||
|
}
|
||
|
matched++;
|
||
|
}
|
||
|
if (matched === needle.length) {
|
||
|
return pin - needle.length + 1;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/repeat.ts
|
||
|
function repeat(source, count) {
|
||
|
if (count === 0) {
|
||
|
return new Uint8Array();
|
||
|
}
|
||
|
if (count < 0) {
|
||
|
throw new RangeError("bytes: negative repeat count");
|
||
|
}
|
||
|
if (!Number.isInteger(count)) {
|
||
|
throw new Error("bytes: repeat count must be an integer");
|
||
|
}
|
||
|
const nb = new Uint8Array(source.length * count);
|
||
|
let bp = copy(source, nb);
|
||
|
for (; bp < nb.length; bp *= 2) {
|
||
|
copy(nb.slice(0, bp), nb, bp);
|
||
|
}
|
||
|
return nb;
|
||
|
}
|
||
|
|
||
|
// https://deno.land/std@0.185.0/bytes/starts_with.ts
|
||
|
function startsWith(source, prefix) {
|
||
|
for (let i = 0, max = prefix.length; i < max; i++) {
|
||
|
if (source[i] !== prefix[i])
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
export {
|
||
|
BytesList,
|
||
|
concat,
|
||
|
copy,
|
||
|
endsWith,
|
||
|
equals,
|
||
|
includesNeedle,
|
||
|
indexOfNeedle,
|
||
|
lastIndexOfNeedle,
|
||
|
repeat,
|
||
|
startsWith
|
||
|
};
|