deno/runtime/js/13_buffer.js
David Sherret 10e4b2e140
chore: update copyright year to 2023 (#17247)
Yearly tradition of creating extra noise in git.
2023-01-02 21:00:42 +00:00

260 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
"use strict";
((window) => {
const { assert } = window.__bootstrap.infra;
const {
TypedArrayPrototypeSubarray,
TypedArrayPrototypeSlice,
TypedArrayPrototypeSet,
MathFloor,
MathMin,
PromiseResolve,
Uint8Array,
Error,
} = window.__bootstrap.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 = dst.byteLength - off;
if (src.byteLength > r) {
src = TypedArrayPrototypeSubarray(src, 0, r);
}
TypedArrayPrototypeSet(dst, src, off);
return src.byteLength;
}
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 this.#buf.byteLength <= this.#off;
}
get length() {
return this.#buf.byteLength - this.#off;
}
get capacity() {
return this.#buf.buffer.byteLength;
}
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 = this.#buf.byteLength;
if (n <= this.capacity - l) {
this.#reslice(l + n);
return l;
}
return -1;
}
#reslice(len) {
assert(len <= this.#buf.buffer.byteLength);
this.#buf = new Uint8Array(this.#buf.buffer, 0, len);
}
readSync(p) {
if (this.empty()) {
// Buffer is empty, reset to recover space.
this.reset();
if (p.byteLength === 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(p.byteLength);
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(this.#buf.buffer, 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(this.#buf.buffer, 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));
}
}
window.__bootstrap.buffer = {
writeAll,
writeAllSync,
readAll,
readAllSync,
Buffer,
};
})(this);