mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
03edd48edd
Closes #17709
257 lines
6.7 KiB
JavaScript
257 lines
6.7 KiB
JavaScript
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
// This code has been ported almost directly from Go's src/bytes/buffer.go
|
|
// Copyright 2009 The Go Authors. All rights reserved. BSD license.
|
|
// https://github.com/golang/go/blob/master/LICENSE
|
|
|
|
import { assert } from "ext:deno_web/00_infra.js";
|
|
const primordials = globalThis.__bootstrap.primordials;
|
|
const {
|
|
ArrayBufferPrototypeGetByteLength,
|
|
TypedArrayPrototypeSubarray,
|
|
TypedArrayPrototypeSlice,
|
|
TypedArrayPrototypeSet,
|
|
TypedArrayPrototypeGetBuffer,
|
|
TypedArrayPrototypeGetByteLength,
|
|
MathFloor,
|
|
MathMin,
|
|
PromiseResolve,
|
|
Uint8Array,
|
|
Error,
|
|
} = primordials;
|
|
|
|
// MIN_READ is the minimum ArrayBuffer size passed to a read call by
|
|
// buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond
|
|
// what is required to hold the contents of r, readFrom() will not grow the
|
|
// underlying buffer.
|
|
const MIN_READ = 32 * 1024;
|
|
const MAX_SIZE = 2 ** 32 - 2;
|
|
|
|
// `off` is the offset into `dst` where it will at which to begin writing values
|
|
// from `src`.
|
|
// Returns the number of bytes copied.
|
|
function copyBytes(src, dst, off = 0) {
|
|
const r = TypedArrayPrototypeGetByteLength(dst) - off;
|
|
if (TypedArrayPrototypeGetByteLength(src) > r) {
|
|
src = TypedArrayPrototypeSubarray(src, 0, r);
|
|
}
|
|
TypedArrayPrototypeSet(dst, src, off);
|
|
return TypedArrayPrototypeGetByteLength(src);
|
|
}
|
|
|
|
class Buffer {
|
|
#buf = null; // contents are the bytes buf[off : len(buf)]
|
|
#off = 0; // read at buf[off], write at buf[buf.byteLength]
|
|
|
|
constructor(ab) {
|
|
if (ab == null) {
|
|
this.#buf = new Uint8Array(0);
|
|
return;
|
|
}
|
|
|
|
this.#buf = new Uint8Array(ab);
|
|
}
|
|
|
|
bytes(options = { copy: true }) {
|
|
if (options.copy === false) {
|
|
return TypedArrayPrototypeSubarray(this.#buf, this.#off);
|
|
}
|
|
return TypedArrayPrototypeSlice(this.#buf, this.#off);
|
|
}
|
|
|
|
empty() {
|
|
return TypedArrayPrototypeGetByteLength(this.#buf) <= this.#off;
|
|
}
|
|
|
|
get length() {
|
|
return TypedArrayPrototypeGetByteLength(this.#buf) - this.#off;
|
|
}
|
|
|
|
get capacity() {
|
|
return ArrayBufferPrototypeGetByteLength(
|
|
TypedArrayPrototypeGetBuffer(this.#buf),
|
|
);
|
|
}
|
|
|
|
truncate(n) {
|
|
if (n === 0) {
|
|
this.reset();
|
|
return;
|
|
}
|
|
if (n < 0 || n > this.length) {
|
|
throw Error("bytes.Buffer: truncation out of range");
|
|
}
|
|
this.#reslice(this.#off + n);
|
|
}
|
|
|
|
reset() {
|
|
this.#reslice(0);
|
|
this.#off = 0;
|
|
}
|
|
|
|
#tryGrowByReslice(n) {
|
|
const l = TypedArrayPrototypeGetByteLength(this.#buf);
|
|
if (n <= this.capacity - l) {
|
|
this.#reslice(l + n);
|
|
return l;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#reslice(len) {
|
|
const ab = TypedArrayPrototypeGetBuffer(this.#buf);
|
|
assert(len <= ArrayBufferPrototypeGetByteLength(ab));
|
|
this.#buf = new Uint8Array(ab, 0, len);
|
|
}
|
|
|
|
readSync(p) {
|
|
if (this.empty()) {
|
|
// Buffer is empty, reset to recover space.
|
|
this.reset();
|
|
if (TypedArrayPrototypeGetByteLength(p) === 0) {
|
|
// this edge case is tested in 'bufferReadEmptyAtEOF' test
|
|
return 0;
|
|
}
|
|
return null;
|
|
}
|
|
const nread = copyBytes(
|
|
TypedArrayPrototypeSubarray(this.#buf, this.#off),
|
|
p,
|
|
);
|
|
this.#off += nread;
|
|
return nread;
|
|
}
|
|
|
|
read(p) {
|
|
const rr = this.readSync(p);
|
|
return PromiseResolve(rr);
|
|
}
|
|
|
|
writeSync(p) {
|
|
const m = this.#grow(TypedArrayPrototypeGetByteLength(p));
|
|
return copyBytes(p, this.#buf, m);
|
|
}
|
|
|
|
write(p) {
|
|
const n = this.writeSync(p);
|
|
return PromiseResolve(n);
|
|
}
|
|
|
|
#grow(n) {
|
|
const m = this.length;
|
|
// If buffer is empty, reset to recover space.
|
|
if (m === 0 && this.#off !== 0) {
|
|
this.reset();
|
|
}
|
|
// Fast: Try to grow by means of a reslice.
|
|
const i = this.#tryGrowByReslice(n);
|
|
if (i >= 0) {
|
|
return i;
|
|
}
|
|
const c = this.capacity;
|
|
if (n <= MathFloor(c / 2) - m) {
|
|
// We can slide things down instead of allocating a new
|
|
// ArrayBuffer. We only need m+n <= c to slide, but
|
|
// we instead let capacity get twice as large so we
|
|
// don't spend all our time copying.
|
|
copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), this.#buf);
|
|
} else if (c + n > MAX_SIZE) {
|
|
throw new Error("The buffer cannot be grown beyond the maximum size.");
|
|
} else {
|
|
// Not enough space anywhere, we need to allocate.
|
|
const buf = new Uint8Array(MathMin(2 * c + n, MAX_SIZE));
|
|
copyBytes(TypedArrayPrototypeSubarray(this.#buf, this.#off), buf);
|
|
this.#buf = buf;
|
|
}
|
|
// Restore this.#off and len(this.#buf).
|
|
this.#off = 0;
|
|
this.#reslice(MathMin(m + n, MAX_SIZE));
|
|
return m;
|
|
}
|
|
|
|
grow(n) {
|
|
if (n < 0) {
|
|
throw Error("Buffer.grow: negative count");
|
|
}
|
|
const m = this.#grow(n);
|
|
this.#reslice(m);
|
|
}
|
|
|
|
async readFrom(r) {
|
|
let n = 0;
|
|
const tmp = new Uint8Array(MIN_READ);
|
|
while (true) {
|
|
const shouldGrow = this.capacity - this.length < MIN_READ;
|
|
// read into tmp buffer if there's not enough room
|
|
// otherwise read directly into the internal buffer
|
|
const buf = shouldGrow
|
|
? tmp
|
|
: new Uint8Array(TypedArrayPrototypeGetBuffer(this.#buf), this.length);
|
|
|
|
const nread = await r.read(buf);
|
|
if (nread === null) {
|
|
return n;
|
|
}
|
|
|
|
// write will grow if needed
|
|
if (shouldGrow) {
|
|
this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread));
|
|
} else this.#reslice(this.length + nread);
|
|
|
|
n += nread;
|
|
}
|
|
}
|
|
|
|
readFromSync(r) {
|
|
let n = 0;
|
|
const tmp = new Uint8Array(MIN_READ);
|
|
while (true) {
|
|
const shouldGrow = this.capacity - this.length < MIN_READ;
|
|
// read into tmp buffer if there's not enough room
|
|
// otherwise read directly into the internal buffer
|
|
const buf = shouldGrow
|
|
? tmp
|
|
: new Uint8Array(TypedArrayPrototypeGetBuffer(this.#buf), this.length);
|
|
|
|
const nread = r.readSync(buf);
|
|
if (nread === null) {
|
|
return n;
|
|
}
|
|
|
|
// write will grow if needed
|
|
if (shouldGrow) {
|
|
this.writeSync(TypedArrayPrototypeSubarray(buf, 0, nread));
|
|
} else this.#reslice(this.length + nread);
|
|
|
|
n += nread;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function readAll(r) {
|
|
const buf = new Buffer();
|
|
await buf.readFrom(r);
|
|
return buf.bytes();
|
|
}
|
|
|
|
function readAllSync(r) {
|
|
const buf = new Buffer();
|
|
buf.readFromSync(r);
|
|
return buf.bytes();
|
|
}
|
|
|
|
async function writeAll(w, arr) {
|
|
let nwritten = 0;
|
|
while (nwritten < arr.length) {
|
|
nwritten += await w.write(TypedArrayPrototypeSubarray(arr, nwritten));
|
|
}
|
|
}
|
|
|
|
function writeAllSync(w, arr) {
|
|
let nwritten = 0;
|
|
while (nwritten < arr.length) {
|
|
nwritten += w.writeSync(TypedArrayPrototypeSubarray(arr, nwritten));
|
|
}
|
|
}
|
|
|
|
export { Buffer, readAll, readAllSync, writeAll, writeAllSync };
|