2018-11-07 13:16:07 -05:00
|
|
|
// 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 * as io from "./io";
|
|
|
|
import { Reader, Writer, ReadResult } from "deno";
|
2018-11-07 14:17:36 -05:00
|
|
|
import { assert, copyBytes } from "./util.ts";
|
2018-11-07 13:16:07 -05:00
|
|
|
|
|
|
|
// 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 = 512;
|
|
|
|
const MAX_SIZE = 2 ** 32 - 2;
|
|
|
|
|
|
|
|
/** A Buffer is a variable-sized buffer of bytes with read() and write()
|
|
|
|
* methods. Based on https://golang.org/pkg/bytes/#Buffer
|
|
|
|
*/
|
|
|
|
export class Buffer implements Reader, Writer {
|
|
|
|
private buf: Uint8Array; // contents are the bytes buf[off : len(buf)]
|
|
|
|
private off = 0; // read at buf[off], write at buf[buf.byteLength]
|
|
|
|
|
|
|
|
constructor(ab?: ArrayBuffer) {
|
|
|
|
if (ab == null) {
|
|
|
|
this.buf = new Uint8Array(0);
|
|
|
|
} else {
|
|
|
|
this.buf = new Uint8Array(ab);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** bytes() returns a slice holding the unread portion of the buffer.
|
|
|
|
* The slice is valid for use only until the next buffer modification (that
|
|
|
|
* is, only until the next call to a method like read(), write(), reset(), or
|
|
|
|
* truncate()). The slice aliases the buffer content at least until the next
|
|
|
|
* buffer modification, so immediate changes to the slice will affect the
|
|
|
|
* result of future reads.
|
|
|
|
*/
|
|
|
|
bytes(): Uint8Array {
|
|
|
|
return this.buf.subarray(this.off);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** toString() returns the contents of the unread portion of the buffer
|
|
|
|
* as a string. Warning - if multibyte characters are present when data is
|
|
|
|
* flowing through the buffer, this method may result in incorrect strings
|
|
|
|
* due to a character being split.
|
|
|
|
*/
|
|
|
|
toString(): string {
|
|
|
|
const decoder = new TextDecoder();
|
|
|
|
return decoder.decode(this.buf.subarray(this.off));
|
|
|
|
}
|
|
|
|
|
|
|
|
/** empty() returns whether the unread portion of the buffer is empty. */
|
|
|
|
empty() {
|
|
|
|
return this.buf.byteLength <= this.off;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** length is a getter that returns the number of bytes of the unread
|
|
|
|
* portion of the buffer
|
|
|
|
*/
|
|
|
|
get length() {
|
|
|
|
return this.buf.byteLength - this.off;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Returns the capacity of the buffer's underlying byte slice, that is,
|
|
|
|
* the total space allocated for the buffer's data.
|
|
|
|
*/
|
|
|
|
get capacity(): number {
|
|
|
|
return this.buf.buffer.byteLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** truncate() discards all but the first n unread bytes from the buffer but
|
|
|
|
* continues to use the same allocated storage. It throws if n is negative or
|
|
|
|
* greater than the length of the buffer.
|
|
|
|
*/
|
|
|
|
truncate(n: number): void {
|
|
|
|
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() resets the buffer to be empty, but it retains the underlying
|
|
|
|
* storage for use by future writes. reset() is the same as truncate(0)
|
|
|
|
*/
|
|
|
|
reset(): void {
|
|
|
|
this._reslice(0);
|
|
|
|
this.off = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** _tryGrowByReslice() is a version of grow for the fast-case
|
|
|
|
* where the internal buffer only needs to be resliced. It returns the index
|
|
|
|
* where bytes should be written and whether it succeeded.
|
|
|
|
* It returns -1 if a reslice was not needed.
|
|
|
|
*/
|
|
|
|
private _tryGrowByReslice(n: number): number {
|
|
|
|
const l = this.buf.byteLength;
|
|
|
|
if (n <= this.capacity - l) {
|
|
|
|
this._reslice(l + n);
|
|
|
|
return l;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
private _reslice(len: number): void {
|
|
|
|
assert(len <= this.buf.buffer.byteLength);
|
|
|
|
this.buf = new Uint8Array(this.buf.buffer, 0, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** read() reads the next len(p) bytes from the buffer or until the buffer
|
|
|
|
* is drained. The return value n is the number of bytes read. If the
|
|
|
|
* buffer has no data to return, eof in the response will be true.
|
|
|
|
*/
|
|
|
|
async read(p: ArrayBufferView): Promise<ReadResult> {
|
|
|
|
if (!(p instanceof Uint8Array)) {
|
|
|
|
throw Error("Only Uint8Array supported");
|
|
|
|
}
|
|
|
|
if (this.empty()) {
|
|
|
|
// Buffer is empty, reset to recover space.
|
|
|
|
this.reset();
|
|
|
|
if (p.byteLength === 0) {
|
|
|
|
// TODO This edge case should be tested by porting TestReadEmptyAtEOF
|
|
|
|
// from the Go tests.
|
|
|
|
return { nread: 0, eof: false };
|
|
|
|
}
|
|
|
|
return { nread: 0, eof: true };
|
|
|
|
}
|
|
|
|
const nread = copyBytes(p, this.buf.subarray(this.off));
|
|
|
|
this.off += nread;
|
|
|
|
return { nread, eof: false };
|
|
|
|
}
|
|
|
|
|
|
|
|
async write(p: ArrayBufferView): Promise<number> {
|
|
|
|
const m = this._grow(p.byteLength);
|
|
|
|
if (!(p instanceof Uint8Array)) {
|
|
|
|
throw Error("Only Uint8Array supported");
|
|
|
|
}
|
|
|
|
return copyBytes(this.buf, p, m);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** _grow() grows the buffer to guarantee space for n more bytes.
|
|
|
|
* It returns the index where bytes should be written.
|
|
|
|
* If the buffer can't grow it will throw with ErrTooLarge.
|
|
|
|
*/
|
|
|
|
private _grow(n: number): number {
|
|
|
|
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, this.buf.subarray(this.off));
|
|
|
|
} else if (c > MAX_SIZE - c - n) {
|
|
|
|
throw Error("ErrTooLarge"); // TODO DenoError(TooLarge)
|
|
|
|
} else {
|
|
|
|
// Not enough space anywhere, we need to allocate.
|
|
|
|
const buf = new Uint8Array(2 * c + n);
|
|
|
|
copyBytes(buf, this.buf.subarray(this.off));
|
|
|
|
this.buf = buf;
|
|
|
|
}
|
|
|
|
// Restore this.off and len(this.buf).
|
|
|
|
this.off = 0;
|
|
|
|
this._reslice(m + n);
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** grow() grows the buffer's capacity, if necessary, to guarantee space for
|
|
|
|
* another n bytes. After grow(n), at least n bytes can be written to the
|
|
|
|
* buffer without another allocation. If n is negative, grow() will panic. If
|
|
|
|
* the buffer can't grow it will throw ErrTooLarge.
|
|
|
|
* Based on https://golang.org/pkg/bytes/#Buffer.Grow
|
|
|
|
*/
|
|
|
|
grow(n: number): void {
|
|
|
|
if (n < 0) {
|
|
|
|
throw Error("Buffer.grow: negative count");
|
|
|
|
}
|
|
|
|
const m = this._grow(n);
|
|
|
|
this._reslice(m);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** readFrom() reads data from r until EOF and appends it to the buffer,
|
|
|
|
* growing the buffer as needed. It returns the number of bytes read. If the
|
|
|
|
* buffer becomes too large, readFrom will panic with ErrTooLarge.
|
|
|
|
* Based on https://golang.org/pkg/bytes/#Buffer.ReadFrom
|
|
|
|
*/
|
|
|
|
async readFrom(r: Reader): Promise<number> {
|
|
|
|
let n = 0;
|
|
|
|
while (true) {
|
|
|
|
try {
|
|
|
|
const i = this._grow(MIN_READ);
|
|
|
|
this._reslice(i);
|
|
|
|
const fub = new Uint8Array(this.buf.buffer, i);
|
|
|
|
const { nread, eof } = await r.read(fub);
|
|
|
|
this._reslice(i + nread);
|
|
|
|
n += nread;
|
|
|
|
if (eof) {
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|