diff options
Diffstat (limited to 'std/io/bufio.ts')
-rw-r--r-- | std/io/bufio.ts | 718 |
1 files changed, 0 insertions, 718 deletions
diff --git a/std/io/bufio.ts b/std/io/bufio.ts deleted file mode 100644 index f3b1893cb..000000000 --- a/std/io/bufio.ts +++ /dev/null @@ -1,718 +0,0 @@ -// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -// 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; -type WriterSync = Deno.WriterSync; -import { copy } from "../bytes/mod.ts"; -import { assert } from "../_util/assert.ts"; - -const DEFAULT_BUF_SIZE = 4096; -const MIN_BUF_SIZE = 16; -const MAX_CONSECUTIVE_EMPTY_READS = 100; -const CR = "\r".charCodeAt(0); -const LF = "\n".charCodeAt(0); - -export class BufferFullError extends Error { - name = "BufferFullError"; - constructor(public partial: Uint8Array) { - super("Buffer full"); - } -} - -export class PartialReadError extends Deno.errors.UnexpectedEof { - name = "PartialReadError"; - partial?: Uint8Array; - constructor() { - super("Encountered UnexpectedEof, data only partially read"); - } -} - -/** 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 === null) { - 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 | null> { - let rr: number | null = 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 ?? 0; - 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 === null) return rr; - assert(rr >= 0, "negative read"); - this.w += rr; - } - - // copy as much as we can - const copied = copy(this.buf.subarray(this.r, this.w), p, 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 `null` 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 | null> { - let bytesRead = 0; - while (bytesRead < p.length) { - try { - const rr = await this.read(p.subarray(bytesRead)); - if (rr === null) { - if (bytesRead === 0) { - return null; - } else { - throw new PartialReadError(); - } - } - bytesRead += rr; - } catch (err) { - err.partial = p.subarray(0, bytesRead); - throw err; - } - } - return p; - } - - /** Returns the next byte [0, 255] or `null`. */ - async readByte(): Promise<number | null> { - while (this.r === this.w) { - if (this.eof) return null; - 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 `null`). - * 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 | null> { - if (delim.length !== 1) { - throw new Error("Delimiter should be a single character"); - } - const buffer = await this.readSlice(delim.charCodeAt(0)); - if (buffer === null) return null; - return new TextDecoder().decode(buffer); - } - - /** `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 `null`. - * - * 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 | null> { - let line: Uint8Array | null; - - 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 === null) { - return null; - } - - 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 `null`. - * - * 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 | null> { - let s = 0; // search start index - let slice: Uint8Array | undefined; - - 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 null; - } - 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; - // #4521 The internal buffer should not be reused across reads because it causes corruption of data. - const oldbuf = this.buf; - const newbuf = this.buf.slice(0); - this.buf = newbuf; - throw new BufferFullError(oldbuf); - } - - 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 `null`. - * - * 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 | null> { - 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 null; - } 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); - } -} - -abstract class AbstractBufBase { - buf!: Uint8Array; - usedBufferBytes = 0; - err: Error | null = null; - - /** Size returns the size of the underlying buffer in bytes. */ - size(): number { - return this.buf.byteLength; - } - - /** Returns how many bytes are unused in the buffer. */ - available(): number { - return this.buf.byteLength - this.usedBufferBytes; - } - - /** buffered returns the number of bytes that have been written into the - * current buffer. - */ - buffered(): number { - return this.usedBufferBytes; - } -} - -/** 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 extends AbstractBufBase implements Writer { - /** return new BufWriter unless writer is BufWriter */ - static create(writer: Writer, size: number = DEFAULT_BUF_SIZE): BufWriter { - return writer instanceof BufWriter ? writer : new BufWriter(writer, size); - } - - constructor(private writer: Writer, size: number = DEFAULT_BUF_SIZE) { - super(); - if (size <= 0) { - size = DEFAULT_BUF_SIZE; - } - this.buf = new Uint8Array(size); - } - - /** Discards any unflushed buffered data, clears any error, and - * resets buffer to write its output to w. - */ - reset(w: Writer): void { - this.err = null; - this.usedBufferBytes = 0; - this.writer = w; - } - - /** Flush writes any buffered data to the underlying io.Writer. */ - async flush(): Promise<void> { - if (this.err !== null) throw this.err; - if (this.usedBufferBytes === 0) return; - - try { - await Deno.writeAll( - this.writer, - this.buf.subarray(0, this.usedBufferBytes), - ); - } catch (e) { - this.err = e; - throw e; - } - - this.buf = new Uint8Array(this.buf.length); - this.usedBufferBytes = 0; - } - - /** Writes the contents of `data` into the buffer. If the contents won't fully - * fit into the buffer, those bytes that can are copied into the buffer, the - * buffer is the flushed to the writer and the remaining bytes are copied into - * the now empty buffer. - * - * @return the number of bytes written to the buffer. - */ - async write(data: Uint8Array): Promise<number> { - if (this.err !== null) throw this.err; - if (data.length === 0) return 0; - - let totalBytesWritten = 0; - let numBytesWritten = 0; - while (data.byteLength > this.available()) { - if (this.buffered() === 0) { - // Large write, empty buffer. - // Write directly from data to avoid copy. - try { - numBytesWritten = await this.writer.write(data); - } catch (e) { - this.err = e; - throw e; - } - } else { - numBytesWritten = copy(data, this.buf, this.usedBufferBytes); - this.usedBufferBytes += numBytesWritten; - await this.flush(); - } - totalBytesWritten += numBytesWritten; - data = data.subarray(numBytesWritten); - } - - numBytesWritten = copy(data, this.buf, this.usedBufferBytes); - this.usedBufferBytes += numBytesWritten; - totalBytesWritten += numBytesWritten; - return totalBytesWritten; - } -} - -/** BufWriterSync implements buffering for a deno.WriterSync object. - * If an error occurs writing to a WriterSync, 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.WriterSync. - */ -export class BufWriterSync extends AbstractBufBase implements WriterSync { - /** return new BufWriterSync unless writer is BufWriterSync */ - static create( - writer: WriterSync, - size: number = DEFAULT_BUF_SIZE, - ): BufWriterSync { - return writer instanceof BufWriterSync - ? writer - : new BufWriterSync(writer, size); - } - - constructor(private writer: WriterSync, size: number = DEFAULT_BUF_SIZE) { - super(); - if (size <= 0) { - size = DEFAULT_BUF_SIZE; - } - this.buf = new Uint8Array(size); - } - - /** Discards any unflushed buffered data, clears any error, and - * resets buffer to write its output to w. - */ - reset(w: WriterSync): void { - this.err = null; - this.usedBufferBytes = 0; - this.writer = w; - } - - /** Flush writes any buffered data to the underlying io.WriterSync. */ - flush(): void { - if (this.err !== null) throw this.err; - if (this.usedBufferBytes === 0) return; - - try { - Deno.writeAllSync( - this.writer, - this.buf.subarray(0, this.usedBufferBytes), - ); - } catch (e) { - this.err = e; - throw e; - } - - this.buf = new Uint8Array(this.buf.length); - this.usedBufferBytes = 0; - } - - /** Writes the contents of `data` into the buffer. If the contents won't fully - * fit into the buffer, those bytes that can are copied into the buffer, the - * buffer is the flushed to the writer and the remaining bytes are copied into - * the now empty buffer. - * - * @return the number of bytes written to the buffer. - */ - writeSync(data: Uint8Array): number { - if (this.err !== null) throw this.err; - if (data.length === 0) return 0; - - let totalBytesWritten = 0; - let numBytesWritten = 0; - while (data.byteLength > this.available()) { - if (this.buffered() === 0) { - // Large write, empty buffer. - // Write directly from data to avoid copy. - try { - numBytesWritten = this.writer.writeSync(data); - } catch (e) { - this.err = e; - throw e; - } - } else { - numBytesWritten = copy(data, this.buf, this.usedBufferBytes); - this.usedBufferBytes += numBytesWritten; - this.flush(); - } - totalBytesWritten += numBytesWritten; - data = data.subarray(numBytesWritten); - } - - numBytesWritten = copy(data, this.buf, this.usedBufferBytes); - this.usedBufferBytes += numBytesWritten; - totalBytesWritten += numBytesWritten; - return totalBytesWritten; - } -} - -/** Generate longest proper prefix which is also suffix array. */ -function createLPS(pat: Uint8Array): Uint8Array { - const lps = new Uint8Array(pat.length); - lps[0] = 0; - let prefixEnd = 0; - let i = 1; - while (i < lps.length) { - if (pat[i] == pat[prefixEnd]) { - prefixEnd++; - lps[i] = prefixEnd; - i++; - } else if (prefixEnd === 0) { - lps[i] = 0; - i++; - } else { - prefixEnd = pat[prefixEnd - 1]; - } - } - return lps; -} - -/** Read delimited bytes from a Reader. */ -export async function* readDelim( - reader: Reader, - delim: Uint8Array, -): AsyncIterableIterator<Uint8Array> { - // Avoid unicode problems - const delimLen = delim.length; - const delimLPS = createLPS(delim); - - let inputBuffer = new Deno.Buffer(); - const inspectArr = new Uint8Array(Math.max(1024, delimLen + 1)); - - // Modified KMP - let inspectIndex = 0; - let matchIndex = 0; - while (true) { - const result = await reader.read(inspectArr); - if (result === null) { - // Yield last chunk. - yield inputBuffer.bytes(); - return; - } - if ((result as number) < 0) { - // Discard all remaining and silently fail. - return; - } - const sliceRead = inspectArr.subarray(0, result as number); - await Deno.writeAll(inputBuffer, sliceRead); - - let sliceToProcess = inputBuffer.bytes(); - while (inspectIndex < sliceToProcess.length) { - if (sliceToProcess[inspectIndex] === delim[matchIndex]) { - inspectIndex++; - matchIndex++; - if (matchIndex === delimLen) { - // Full match - const matchEnd = inspectIndex - delimLen; - const readyBytes = sliceToProcess.subarray(0, matchEnd); - // Copy - const pendingBytes = sliceToProcess.slice(inspectIndex); - yield readyBytes; - // Reset match, different from KMP. - sliceToProcess = pendingBytes; - inspectIndex = 0; - matchIndex = 0; - } - } else { - if (matchIndex === 0) { - inspectIndex++; - } else { - matchIndex = delimLPS[matchIndex - 1]; - } - } - } - // Keep inspectIndex and matchIndex. - inputBuffer = new Deno.Buffer(sliceToProcess); - } -} - -/** Read delimited strings from a Reader. */ -export async function* readStringDelim( - reader: Reader, - delim: string, -): AsyncIterableIterator<string> { - const encoder = new TextEncoder(); - const decoder = new TextDecoder(); - for await (const chunk of readDelim(reader, encoder.encode(delim))) { - yield decoder.decode(chunk); - } -} - -/** Read strings line-by-line from a Reader. */ -export async function* readLines( - reader: Reader, -): AsyncIterableIterator<string> { - for await (let chunk of readStringDelim(reader, "\n")) { - // Finding a CR at the end of the line is evidence of a - // "\r\n" at the end of the line. The "\r" part should be - // removed too. - if (chunk.endsWith("\r")) { - chunk = chunk.slice(0, -1); - } - yield chunk; - } -} |