summaryrefslogtreecommitdiff
path: root/std/io/bufio.ts
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2019-10-09 17:10:09 -0400
committerRyan Dahl <ry@tinyclouds.org>2019-10-09 17:10:09 -0400
commit151ce0266eb4de2c8fc600c81c192a5f791b6169 (patch)
tree7cb04016a1c7ee88adde83814548d7a9409dcde3 /std/io/bufio.ts
parenta355f7c807686918734416d91b79c26c21effba9 (diff)
Move everything into std subdir
Diffstat (limited to 'std/io/bufio.ts')
-rw-r--r--std/io/bufio.ts509
1 files changed, 509 insertions, 0 deletions
diff --git a/std/io/bufio.ts b/std/io/bufio.ts
new file mode 100644
index 000000000..213870c3c
--- /dev/null
+++ b/std/io/bufio.ts
@@ -0,0 +1,509 @@
+// Based on https://github.com/golang/go/blob/891682/src/bufio/bufio.go
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+type Reader = Deno.Reader;
+type Writer = Deno.Writer;
+import { charCode, copyBytes } from "./util.ts";
+import { assert } from "../testing/asserts.ts";
+
+const DEFAULT_BUF_SIZE = 4096;
+const MIN_BUF_SIZE = 16;
+const MAX_CONSECUTIVE_EMPTY_READS = 100;
+const CR = charCode("\r");
+const LF = charCode("\n");
+
+export class BufferFullError extends Error {
+ name = "BufferFullError";
+ constructor(public partial: Uint8Array) {
+ super("Buffer full");
+ }
+}
+
+export class UnexpectedEOFError extends Error {
+ name = "UnexpectedEOFError";
+ constructor() {
+ super("Unexpected EOF");
+ }
+}
+
+/** Result type returned by of BufReader.readLine(). */
+export interface ReadLineResult {
+ line: Uint8Array;
+ more: boolean;
+}
+
+/** BufReader implements buffering for a Reader object. */
+export class BufReader implements Reader {
+ private buf!: Uint8Array;
+ private rd!: Reader; // Reader provided by caller.
+ private r = 0; // buf read position.
+ private w = 0; // buf write position.
+ private eof = false;
+ // private lastByte: number;
+ // private lastCharSize: number;
+
+ /** return new BufReader unless r is BufReader */
+ static create(r: Reader, size: number = DEFAULT_BUF_SIZE): BufReader {
+ return r instanceof BufReader ? r : new BufReader(r, size);
+ }
+
+ constructor(rd: Reader, size: number = DEFAULT_BUF_SIZE) {
+ if (size < MIN_BUF_SIZE) {
+ size = MIN_BUF_SIZE;
+ }
+ this._reset(new Uint8Array(size), rd);
+ }
+
+ /** Returns the size of the underlying buffer in bytes. */
+ size(): number {
+ return this.buf.byteLength;
+ }
+
+ buffered(): number {
+ return this.w - this.r;
+ }
+
+ // Reads a new chunk into the buffer.
+ private async _fill(): Promise<void> {
+ // Slide existing data to beginning.
+ if (this.r > 0) {
+ this.buf.copyWithin(0, this.r, this.w);
+ this.w -= this.r;
+ this.r = 0;
+ }
+
+ if (this.w >= this.buf.byteLength) {
+ throw Error("bufio: tried to fill full buffer");
+ }
+
+ // Read new data: try a limited number of times.
+ for (let i = MAX_CONSECUTIVE_EMPTY_READS; i > 0; i--) {
+ const rr = await this.rd.read(this.buf.subarray(this.w));
+ if (rr === Deno.EOF) {
+ this.eof = true;
+ return;
+ }
+ assert(rr >= 0, "negative read");
+ this.w += rr;
+ if (rr > 0) {
+ return;
+ }
+ }
+
+ throw new Error(
+ `No progress after ${MAX_CONSECUTIVE_EMPTY_READS} read() calls`
+ );
+ }
+
+ /** Discards any buffered data, resets all state, and switches
+ * the buffered reader to read from r.
+ */
+ reset(r: Reader): void {
+ this._reset(this.buf, r);
+ }
+
+ private _reset(buf: Uint8Array, rd: Reader): void {
+ this.buf = buf;
+ this.rd = rd;
+ this.eof = false;
+ // this.lastByte = -1;
+ // this.lastCharSize = -1;
+ }
+
+ /** reads data into p.
+ * It returns the number of bytes read into p.
+ * The bytes are taken from at most one Read on the underlying Reader,
+ * hence n may be less than len(p).
+ * To read exactly len(p) bytes, use io.ReadFull(b, p).
+ */
+ async read(p: Uint8Array): Promise<number | Deno.EOF> {
+ let rr: number | Deno.EOF = p.byteLength;
+ if (p.byteLength === 0) return rr;
+
+ if (this.r === this.w) {
+ if (p.byteLength >= this.buf.byteLength) {
+ // Large read, empty buffer.
+ // Read directly into p to avoid copy.
+ const rr = await this.rd.read(p);
+ const nread = rr === Deno.EOF ? 0 : rr;
+ assert(nread >= 0, "negative read");
+ // if (rr.nread > 0) {
+ // this.lastByte = p[rr.nread - 1];
+ // this.lastCharSize = -1;
+ // }
+ return rr;
+ }
+
+ // One read.
+ // Do not use this.fill, which will loop.
+ this.r = 0;
+ this.w = 0;
+ rr = await this.rd.read(this.buf);
+ if (rr === 0 || rr === Deno.EOF) return rr;
+ assert(rr >= 0, "negative read");
+ this.w += rr;
+ }
+
+ // copy as much as we can
+ const copied = copyBytes(p, this.buf.subarray(this.r, this.w), 0);
+ this.r += copied;
+ // this.lastByte = this.buf[this.r - 1];
+ // this.lastCharSize = -1;
+ return copied;
+ }
+
+ /** reads exactly `p.length` bytes into `p`.
+ *
+ * If successful, `p` is returned.
+ *
+ * If the end of the underlying stream has been reached, and there are no more
+ * bytes available in the buffer, `readFull()` returns `EOF` instead.
+ *
+ * An error is thrown if some bytes could be read, but not enough to fill `p`
+ * entirely before the underlying stream reported an error or EOF. Any error
+ * thrown will have a `partial` property that indicates the slice of the
+ * buffer that has been successfully filled with data.
+ *
+ * Ported from https://golang.org/pkg/io/#ReadFull
+ */
+ async readFull(p: Uint8Array): Promise<Uint8Array | Deno.EOF> {
+ let bytesRead = 0;
+ while (bytesRead < p.length) {
+ try {
+ const rr = await this.read(p.subarray(bytesRead));
+ if (rr === Deno.EOF) {
+ if (bytesRead === 0) {
+ return Deno.EOF;
+ } else {
+ throw new UnexpectedEOFError();
+ }
+ }
+ bytesRead += rr;
+ } catch (err) {
+ err.partial = p.subarray(0, bytesRead);
+ throw err;
+ }
+ }
+ return p;
+ }
+
+ /** Returns the next byte [0, 255] or `EOF`. */
+ async readByte(): Promise<number | Deno.EOF> {
+ while (this.r === this.w) {
+ if (this.eof) return Deno.EOF;
+ await this._fill(); // buffer is empty.
+ }
+ const c = this.buf[this.r];
+ this.r++;
+ // this.lastByte = c;
+ return c;
+ }
+
+ /** readString() reads until the first occurrence of delim in the input,
+ * returning a string containing the data up to and including the delimiter.
+ * If ReadString encounters an error before finding a delimiter,
+ * it returns the data read before the error and the error itself
+ * (often io.EOF).
+ * ReadString returns err != nil if and only if the returned data does not end
+ * in
+ * delim.
+ * For simple uses, a Scanner may be more convenient.
+ */
+ async readString(delim: string): Promise<string | Deno.EOF> {
+ if (delim.length !== 1)
+ throw new Error("Delimiter should be a single character");
+ const buffer = await this.readSlice(delim.charCodeAt(0));
+ return new TextDecoder().decode(buffer || undefined);
+ }
+
+ /** `readLine()` is a low-level line-reading primitive. Most callers should
+ * use `readString('\n')` instead or use a Scanner.
+ *
+ * `readLine()` tries to return a single line, not including the end-of-line
+ * bytes. If the line was too long for the buffer then `more` is set and the
+ * beginning of the line is returned. The rest of the line will be returned
+ * from future calls. `more` will be false when returning the last fragment
+ * of the line. The returned buffer is only valid until the next call to
+ * `readLine()`.
+ *
+ * The text returned from ReadLine does not include the line end ("\r\n" or
+ * "\n").
+ *
+ * When the end of the underlying stream is reached, the final bytes in the
+ * stream are returned. No indication or error is given if the input ends
+ * without a final line end. When there are no more trailing bytes to read,
+ * `readLine()` returns the `EOF` symbol.
+ *
+ * Calling `unreadByte()` after `readLine()` will always unread the last byte
+ * read (possibly a character belonging to the line end) even if that byte is
+ * not part of the line returned by `readLine()`.
+ */
+ async readLine(): Promise<ReadLineResult | Deno.EOF> {
+ let line: Uint8Array | Deno.EOF;
+
+ try {
+ line = await this.readSlice(LF);
+ } catch (err) {
+ let { partial } = err;
+ assert(
+ partial instanceof Uint8Array,
+ "bufio: caught error from `readSlice()` without `partial` property"
+ );
+
+ // Don't throw if `readSlice()` failed with `BufferFullError`, instead we
+ // just return whatever is available and set the `more` flag.
+ if (!(err instanceof BufferFullError)) {
+ throw err;
+ }
+
+ // Handle the case where "\r\n" straddles the buffer.
+ if (
+ !this.eof &&
+ partial.byteLength > 0 &&
+ partial[partial.byteLength - 1] === CR
+ ) {
+ // Put the '\r' back on buf and drop it from line.
+ // Let the next call to ReadLine check for "\r\n".
+ assert(this.r > 0, "bufio: tried to rewind past start of buffer");
+ this.r--;
+ partial = partial.subarray(0, partial.byteLength - 1);
+ }
+
+ return { line: partial, more: !this.eof };
+ }
+
+ if (line === Deno.EOF) {
+ return Deno.EOF;
+ }
+
+ if (line.byteLength === 0) {
+ return { line, more: false };
+ }
+
+ if (line[line.byteLength - 1] == LF) {
+ let drop = 1;
+ if (line.byteLength > 1 && line[line.byteLength - 2] === CR) {
+ drop = 2;
+ }
+ line = line.subarray(0, line.byteLength - drop);
+ }
+ return { line, more: false };
+ }
+
+ /** `readSlice()` reads until the first occurrence of `delim` in the input,
+ * returning a slice pointing at the bytes in the buffer. The bytes stop
+ * being valid at the next read.
+ *
+ * If `readSlice()` encounters an error before finding a delimiter, or the
+ * buffer fills without finding a delimiter, it throws an error with a
+ * `partial` property that contains the entire buffer.
+ *
+ * If `readSlice()` encounters the end of the underlying stream and there are
+ * any bytes left in the buffer, the rest of the buffer is returned. In other
+ * words, EOF is always treated as a delimiter. Once the buffer is empty,
+ * it returns `EOF`.
+ *
+ * Because the data returned from `readSlice()` will be overwritten by the
+ * next I/O operation, most clients should use `readString()` instead.
+ */
+ async readSlice(delim: number): Promise<Uint8Array | Deno.EOF> {
+ let s = 0; // search start index
+ let slice: Uint8Array;
+
+ while (true) {
+ // Search buffer.
+ let i = this.buf.subarray(this.r + s, this.w).indexOf(delim);
+ if (i >= 0) {
+ i += s;
+ slice = this.buf.subarray(this.r, this.r + i + 1);
+ this.r += i + 1;
+ break;
+ }
+
+ // EOF?
+ if (this.eof) {
+ if (this.r === this.w) {
+ return Deno.EOF;
+ }
+ slice = this.buf.subarray(this.r, this.w);
+ this.r = this.w;
+ break;
+ }
+
+ // Buffer full?
+ if (this.buffered() >= this.buf.byteLength) {
+ this.r = this.w;
+ throw new BufferFullError(this.buf);
+ }
+
+ s = this.w - this.r; // do not rescan area we scanned before
+
+ // Buffer is not full.
+ try {
+ await this._fill();
+ } catch (err) {
+ err.partial = slice!;
+ throw err;
+ }
+ }
+
+ // Handle last byte, if any.
+ // const i = slice.byteLength - 1;
+ // if (i >= 0) {
+ // this.lastByte = slice[i];
+ // this.lastCharSize = -1
+ // }
+
+ return slice;
+ }
+
+ /** `peek()` returns the next `n` bytes without advancing the reader. The
+ * bytes stop being valid at the next read call.
+ *
+ * When the end of the underlying stream is reached, but there are unread
+ * bytes left in the buffer, those bytes are returned. If there are no bytes
+ * left in the buffer, it returns `EOF`.
+ *
+ * If an error is encountered before `n` bytes are available, `peek()` throws
+ * an error with the `partial` property set to a slice of the buffer that
+ * contains the bytes that were available before the error occurred.
+ */
+ async peek(n: number): Promise<Uint8Array | Deno.EOF> {
+ if (n < 0) {
+ throw Error("negative count");
+ }
+
+ let avail = this.w - this.r;
+ while (avail < n && avail < this.buf.byteLength && !this.eof) {
+ try {
+ await this._fill();
+ } catch (err) {
+ err.partial = this.buf.subarray(this.r, this.w);
+ throw err;
+ }
+ avail = this.w - this.r;
+ }
+
+ if (avail === 0 && this.eof) {
+ return Deno.EOF;
+ } else if (avail < n && this.eof) {
+ return this.buf.subarray(this.r, this.r + avail);
+ } else if (avail < n) {
+ throw new BufferFullError(this.buf.subarray(this.r, this.w));
+ }
+
+ return this.buf.subarray(this.r, this.r + n);
+ }
+}
+
+/** BufWriter implements buffering for an deno.Writer object.
+ * If an error occurs writing to a Writer, no more data will be
+ * accepted and all subsequent writes, and flush(), will return the error.
+ * After all data has been written, the client should call the
+ * flush() method to guarantee all data has been forwarded to
+ * the underlying deno.Writer.
+ */
+export class BufWriter implements Writer {
+ buf: Uint8Array;
+ n = 0;
+ err: Error | null = null;
+
+ /** return new BufWriter unless w is BufWriter */
+ static create(w: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter {
+ return w instanceof BufWriter ? w : new BufWriter(w, size);
+ }
+
+ constructor(private wr: Writer, size: number = DEFAULT_BUF_SIZE) {
+ if (size <= 0) {
+ size = DEFAULT_BUF_SIZE;
+ }
+ this.buf = new Uint8Array(size);
+ }
+
+ /** Size returns the size of the underlying buffer in bytes. */
+ size(): number {
+ return this.buf.byteLength;
+ }
+
+ /** Discards any unflushed buffered data, clears any error, and
+ * resets b to write its output to w.
+ */
+ reset(w: Writer): void {
+ this.err = null;
+ this.n = 0;
+ this.wr = w;
+ }
+
+ /** Flush writes any buffered data to the underlying io.Writer. */
+ async flush(): Promise<void> {
+ if (this.err !== null) throw this.err;
+ if (this.n === 0) return;
+
+ let n = 0;
+ try {
+ n = await this.wr.write(this.buf.subarray(0, this.n));
+ } catch (e) {
+ this.err = e;
+ throw e;
+ }
+
+ if (n < this.n) {
+ if (n > 0) {
+ this.buf.copyWithin(0, n, this.n);
+ this.n -= n;
+ }
+ this.err = new Error("Short write");
+ throw this.err;
+ }
+
+ this.n = 0;
+ }
+
+ /** Returns how many bytes are unused in the buffer. */
+ available(): number {
+ return this.buf.byteLength - this.n;
+ }
+
+ /** buffered returns the number of bytes that have been written into the
+ * current buffer.
+ */
+ buffered(): number {
+ return this.n;
+ }
+
+ /** Writes the contents of p into the buffer.
+ * Returns the number of bytes written.
+ */
+ async write(p: Uint8Array): Promise<number> {
+ if (this.err !== null) throw this.err;
+ if (p.length === 0) return 0;
+
+ let nn = 0;
+ let n = 0;
+ while (p.byteLength > this.available()) {
+ if (this.buffered() === 0) {
+ // Large write, empty buffer.
+ // Write directly from p to avoid copy.
+ try {
+ n = await this.wr.write(p);
+ } catch (e) {
+ this.err = e;
+ throw e;
+ }
+ } else {
+ n = copyBytes(this.buf, p, this.n);
+ this.n += n;
+ await this.flush();
+ }
+ nn += n;
+ p = p.subarray(n);
+ }
+
+ n = copyBytes(this.buf, p, this.n);
+ this.n += n;
+ nn += n;
+ return nn;
+ }
+}