diff options
Diffstat (limited to 'runtime/js/13_buffer.js')
-rw-r--r-- | runtime/js/13_buffer.js | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/runtime/js/13_buffer.js b/runtime/js/13_buffer.js new file mode 100644 index 000000000..e06e2138b --- /dev/null +++ b/runtime/js/13_buffer.js @@ -0,0 +1,241 @@ +// Copyright 2018-2020 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 + +((window) => { + const { assert } = window.__bootstrap.util; + + // 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 = src.subarray(0, r); + } + dst.set(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 this.#buf.subarray(this.#off); + return this.#buf.slice(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(this.#buf.subarray(this.#off), p); + this.#off += nread; + return nread; + } + + read(p) { + const rr = this.readSync(p); + return Promise.resolve(rr); + } + + writeSync(p) { + const m = this.#grow(p.byteLength); + return copyBytes(p, this.#buf, m); + } + + write(p) { + const n = this.writeSync(p); + return Promise.resolve(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 <= Math.floor(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(this.#buf.subarray(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(Math.min(2 * c + n, MAX_SIZE)); + copyBytes(this.#buf.subarray(this.#off), buf); + this.#buf = buf; + } + // Restore this.#off and len(this.#buf). + this.#off = 0; + this.#reslice(Math.min(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(buf.subarray(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(buf.subarray(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(arr.subarray(nwritten)); + } + } + + function writeAllSync(w, arr) { + let nwritten = 0; + while (nwritten < arr.length) { + nwritten += w.writeSync(arr.subarray(nwritten)); + } + } + + window.__bootstrap.buffer = { + writeAll, + writeAllSync, + readAll, + readAllSync, + Buffer, + }; +})(this); |