mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 00:29:09 -05:00
parent
5f74f7eebe
commit
d35e13e6be
2 changed files with 106 additions and 47 deletions
62
bufio.ts
62
bufio.ts
|
@ -12,11 +12,7 @@ const MAX_CONSECUTIVE_EMPTY_READS = 100;
|
||||||
const CR = charCode("\r");
|
const CR = charCode("\r");
|
||||||
const LF = charCode("\n");
|
const LF = charCode("\n");
|
||||||
|
|
||||||
export enum BufState {
|
export type BufState = null | "EOF" | "BufferFull" | "NoProgress" | Error;
|
||||||
Ok,
|
|
||||||
EOF,
|
|
||||||
BufferFull
|
|
||||||
}
|
|
||||||
|
|
||||||
/** BufReader implements buffering for a Reader object. */
|
/** BufReader implements buffering for a Reader object. */
|
||||||
export class BufReader implements Reader {
|
export class BufReader implements Reader {
|
||||||
|
@ -26,7 +22,7 @@ export class BufReader implements Reader {
|
||||||
private w = 0; // buf write position.
|
private w = 0; // buf write position.
|
||||||
private lastByte: number;
|
private lastByte: number;
|
||||||
private lastCharSize: number;
|
private lastCharSize: number;
|
||||||
private err: null | Error;
|
private err: BufState;
|
||||||
|
|
||||||
constructor(rd: Reader, size = DEFAULT_BUF_SIZE) {
|
constructor(rd: Reader, size = DEFAULT_BUF_SIZE) {
|
||||||
if (size < MIN_BUF_SIZE) {
|
if (size < MIN_BUF_SIZE) {
|
||||||
|
@ -44,7 +40,7 @@ export class BufReader implements Reader {
|
||||||
return this.w - this.r;
|
return this.w - this.r;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _readErr(): Error {
|
private _readErr(): BufState {
|
||||||
const err = this.err;
|
const err = this.err;
|
||||||
this.err = null;
|
this.err = null;
|
||||||
return err;
|
return err;
|
||||||
|
@ -52,7 +48,7 @@ export class BufReader implements Reader {
|
||||||
|
|
||||||
// Reads a new chunk into the buffer.
|
// Reads a new chunk into the buffer.
|
||||||
// Returns true if EOF, false on successful read.
|
// Returns true if EOF, false on successful read.
|
||||||
private async _fill(): Promise<BufState> {
|
private async _fill(): Promise<void> {
|
||||||
// Slide existing data to beginning.
|
// Slide existing data to beginning.
|
||||||
if (this.r > 0) {
|
if (this.r > 0) {
|
||||||
this.buf.copyWithin(0, this.r, this.w);
|
this.buf.copyWithin(0, this.r, this.w);
|
||||||
|
@ -71,18 +67,19 @@ export class BufReader implements Reader {
|
||||||
rr = await this.rd.read(this.buf.subarray(this.w));
|
rr = await this.rd.read(this.buf.subarray(this.w));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.err = e;
|
this.err = e;
|
||||||
return BufState.Ok;
|
return;
|
||||||
}
|
}
|
||||||
assert(rr.nread >= 0, "negative read");
|
assert(rr.nread >= 0, "negative read");
|
||||||
this.w += rr.nread;
|
this.w += rr.nread;
|
||||||
if (rr.eof) {
|
if (rr.eof) {
|
||||||
return BufState.EOF;
|
this.err = "EOF";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (rr.nread > 0) {
|
if (rr.nread > 0) {
|
||||||
return BufState.Ok;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw Error("No Progress");
|
this.err = "NoProgress";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Discards any buffered data, resets all state, and switches
|
/** Discards any buffered data, resets all state, and switches
|
||||||
|
@ -96,7 +93,7 @@ export class BufReader implements Reader {
|
||||||
this.buf = buf;
|
this.buf = buf;
|
||||||
this.rd = rd;
|
this.rd = rd;
|
||||||
this.lastByte = -1;
|
this.lastByte = -1;
|
||||||
this.lastCharSize = -1;
|
// this.lastRuneSize = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** reads data into p.
|
/** reads data into p.
|
||||||
|
@ -163,13 +160,13 @@ export class BufReader implements Reader {
|
||||||
/** Returns the next byte [0, 255] or -1 if EOF. */
|
/** Returns the next byte [0, 255] or -1 if EOF. */
|
||||||
async readByte(): Promise<number> {
|
async readByte(): Promise<number> {
|
||||||
while (this.r === this.w) {
|
while (this.r === this.w) {
|
||||||
const eof = await this._fill(); // buffer is empty.
|
await this._fill(); // buffer is empty.
|
||||||
|
if (this.err == "EOF") {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
if (this.err != null) {
|
if (this.err != null) {
|
||||||
throw this._readErr();
|
throw this._readErr();
|
||||||
}
|
}
|
||||||
if (eof) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const c = this.buf[this.r];
|
const c = this.buf[this.r];
|
||||||
this.r++;
|
this.r++;
|
||||||
|
@ -206,20 +203,10 @@ export class BufReader implements Reader {
|
||||||
* (possibly a character belonging to the line end) even if that byte is not
|
* (possibly a character belonging to the line end) even if that byte is not
|
||||||
* part of the line returned by ReadLine.
|
* part of the line returned by ReadLine.
|
||||||
*/
|
*/
|
||||||
async readLine(): Promise<{
|
async readLine(): Promise<[Uint8Array, boolean, BufState]> {
|
||||||
line?: Uint8Array;
|
let [line, err] = await this.readSlice(LF);
|
||||||
isPrefix: boolean;
|
|
||||||
state: BufState;
|
|
||||||
}> {
|
|
||||||
let line: Uint8Array;
|
|
||||||
let state: BufState;
|
|
||||||
try {
|
|
||||||
[line, state] = await this.readSlice(LF);
|
|
||||||
} catch (err) {
|
|
||||||
this.err = err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === BufState.BufferFull) {
|
if (err === "BufferFull") {
|
||||||
// Handle the case where "\r\n" straddles the buffer.
|
// Handle the case where "\r\n" straddles the buffer.
|
||||||
if (line.byteLength > 0 && line[line.byteLength - 1] === CR) {
|
if (line.byteLength > 0 && line[line.byteLength - 1] === CR) {
|
||||||
// Put the '\r' back on buf and drop it from line.
|
// Put the '\r' back on buf and drop it from line.
|
||||||
|
@ -228,12 +215,13 @@ export class BufReader implements Reader {
|
||||||
this.r--;
|
this.r--;
|
||||||
line = line.subarray(0, line.byteLength - 1);
|
line = line.subarray(0, line.byteLength - 1);
|
||||||
}
|
}
|
||||||
return { line, isPrefix: true, state };
|
return [line, true, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.byteLength === 0) {
|
if (line.byteLength === 0) {
|
||||||
return { line, isPrefix: false, state };
|
return [line, false, err];
|
||||||
}
|
}
|
||||||
|
err = null;
|
||||||
|
|
||||||
if (line[line.byteLength - 1] == LF) {
|
if (line[line.byteLength - 1] == LF) {
|
||||||
let drop = 1;
|
let drop = 1;
|
||||||
|
@ -242,7 +230,7 @@ export class BufReader implements Reader {
|
||||||
}
|
}
|
||||||
line = line.subarray(0, line.byteLength - drop);
|
line = line.subarray(0, line.byteLength - drop);
|
||||||
}
|
}
|
||||||
return { line, isPrefix: false, state };
|
return [line, false, err];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** readSlice() reads until the first occurrence of delim in the input,
|
/** readSlice() reads until the first occurrence of delim in the input,
|
||||||
|
@ -258,7 +246,7 @@ export class BufReader implements Reader {
|
||||||
async readSlice(delim: number): Promise<[Uint8Array, BufState]> {
|
async readSlice(delim: number): Promise<[Uint8Array, BufState]> {
|
||||||
let s = 0; // search start index
|
let s = 0; // search start index
|
||||||
let line: Uint8Array;
|
let line: Uint8Array;
|
||||||
let state = BufState.Ok;
|
let err: BufState;
|
||||||
while (true) {
|
while (true) {
|
||||||
// Search buffer.
|
// Search buffer.
|
||||||
let i = this.buf.subarray(this.r + s, this.w).indexOf(delim);
|
let i = this.buf.subarray(this.r + s, this.w).indexOf(delim);
|
||||||
|
@ -273,7 +261,7 @@ export class BufReader implements Reader {
|
||||||
if (this.err) {
|
if (this.err) {
|
||||||
line = this.buf.subarray(this.r, this.w);
|
line = this.buf.subarray(this.r, this.w);
|
||||||
this.r = this.w;
|
this.r = this.w;
|
||||||
throw this._readErr();
|
err = this._readErr();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +269,7 @@ export class BufReader implements Reader {
|
||||||
if (this.buffered() >= this.buf.byteLength) {
|
if (this.buffered() >= this.buf.byteLength) {
|
||||||
this.r = this.w;
|
this.r = this.w;
|
||||||
line = this.buf;
|
line = this.buf;
|
||||||
state = BufState.BufferFull;
|
err = "BufferFull";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,6 +285,6 @@ export class BufReader implements Reader {
|
||||||
// this.lastRuneSize = -1
|
// this.lastRuneSize = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
return [line, state];
|
return [line, err];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,18 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
import * as deno from "deno";
|
import { Reader, ReadResult } from "deno";
|
||||||
import { test, assertEqual } from "https://deno.land/x/testing/testing.ts";
|
import {
|
||||||
|
test,
|
||||||
|
assert,
|
||||||
|
assertEqual
|
||||||
|
} from "https://deno.land/x/testing/testing.ts";
|
||||||
import { BufReader, BufState } from "./bufio.ts";
|
import { BufReader, BufState } from "./bufio.ts";
|
||||||
import { Buffer } from "./buffer.ts";
|
import { Buffer } from "./buffer.ts";
|
||||||
import * as iotest from "./iotest.ts";
|
import * as iotest from "./iotest.ts";
|
||||||
import { charCode } from "./util.ts";
|
import { charCode, copyBytes } from "./util.ts";
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
|
||||||
async function readBytes(buf: BufReader): Promise<string> {
|
async function readBytes(buf: BufReader): Promise<string> {
|
||||||
const b = new Uint8Array(1000);
|
const b = new Uint8Array(1000);
|
||||||
|
@ -25,8 +31,7 @@ async function readBytes(buf: BufReader): Promise<string> {
|
||||||
return decoder.decode(b.subarray(0, nb));
|
return decoder.decode(b.subarray(0, nb));
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringsReader(s: string): deno.Reader {
|
function stringsReader(s: string): Reader {
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const ui8 = encoder.encode(s);
|
const ui8 = encoder.encode(s);
|
||||||
return new Buffer(ui8.buffer as ArrayBuffer);
|
return new Buffer(ui8.buffer as ArrayBuffer);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +43,7 @@ test(async function bufioReaderSimple() {
|
||||||
assertEqual(s, data);
|
assertEqual(s, data);
|
||||||
});
|
});
|
||||||
|
|
||||||
type ReadMaker = { name: string; fn: (r: deno.Reader) => deno.Reader };
|
type ReadMaker = { name: string; fn: (r: Reader) => Reader };
|
||||||
|
|
||||||
const readMakers: ReadMaker[] = [
|
const readMakers: ReadMaker[] = [
|
||||||
{ name: "full", fn: r => r },
|
{ name: "full", fn: r => r },
|
||||||
|
@ -134,15 +139,81 @@ test(async function bufioBufferFull() {
|
||||||
const longString =
|
const longString =
|
||||||
"And now, hello, world! It is the time for all good men to come to the aid of their party";
|
"And now, hello, world! It is the time for all good men to come to the aid of their party";
|
||||||
const buf = new BufReader(stringsReader(longString), MIN_READ_BUFFER_SIZE);
|
const buf = new BufReader(stringsReader(longString), MIN_READ_BUFFER_SIZE);
|
||||||
let [line, state] = await buf.readSlice(charCode("!"));
|
let [line, err] = await buf.readSlice(charCode("!"));
|
||||||
|
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
let actual = decoder.decode(line);
|
let actual = decoder.decode(line);
|
||||||
assertEqual(state, BufState.BufferFull);
|
assertEqual(err, "BufferFull");
|
||||||
assertEqual(actual, "And now, hello, ");
|
assertEqual(actual, "And now, hello, ");
|
||||||
|
|
||||||
[line, state] = await buf.readSlice(charCode("!"));
|
[line, err] = await buf.readSlice(charCode("!"));
|
||||||
actual = decoder.decode(line);
|
actual = decoder.decode(line);
|
||||||
assertEqual(actual, "world!");
|
assertEqual(actual, "world!");
|
||||||
assertEqual(state, BufState.Ok);
|
assert(err == null);
|
||||||
|
});
|
||||||
|
|
||||||
|
const testInput = encoder.encode(
|
||||||
|
"012\n345\n678\n9ab\ncde\nfgh\nijk\nlmn\nopq\nrst\nuvw\nxy"
|
||||||
|
);
|
||||||
|
const testInputrn = encoder.encode(
|
||||||
|
"012\r\n345\r\n678\r\n9ab\r\ncde\r\nfgh\r\nijk\r\nlmn\r\nopq\r\nrst\r\nuvw\r\nxy\r\n\n\r\n"
|
||||||
|
);
|
||||||
|
const testOutput = encoder.encode("0123456789abcdefghijklmnopqrstuvwxy");
|
||||||
|
|
||||||
|
// TestReader wraps a Uint8Array and returns reads of a specific length.
|
||||||
|
class TestReader implements Reader {
|
||||||
|
constructor(private data: Uint8Array, private stride: number) {}
|
||||||
|
|
||||||
|
async read(buf: ArrayBufferView): Promise<ReadResult> {
|
||||||
|
let nread = this.stride;
|
||||||
|
if (nread > this.data.byteLength) {
|
||||||
|
nread = this.data.byteLength;
|
||||||
|
}
|
||||||
|
if (nread > buf.byteLength) {
|
||||||
|
nread = buf.byteLength;
|
||||||
|
}
|
||||||
|
copyBytes(buf as Uint8Array, this.data);
|
||||||
|
this.data = this.data.subarray(nread);
|
||||||
|
let eof = false;
|
||||||
|
if (this.data.byteLength == 0) {
|
||||||
|
eof = true;
|
||||||
|
}
|
||||||
|
return { nread, eof };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testReadLine(input: Uint8Array): Promise<void> {
|
||||||
|
for (let stride = 1; stride < 2; stride++) {
|
||||||
|
let done = 0;
|
||||||
|
let reader = new TestReader(input, stride);
|
||||||
|
let l = new BufReader(reader, input.byteLength + 1);
|
||||||
|
while (true) {
|
||||||
|
let [line, isPrefix, err] = await l.readLine();
|
||||||
|
if (line.byteLength > 0 && err != null) {
|
||||||
|
throw Error("readLine returned both data and error");
|
||||||
|
}
|
||||||
|
assertEqual(isPrefix, false);
|
||||||
|
if (err == "EOF") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let want = testOutput.subarray(done, done + line.byteLength);
|
||||||
|
assertEqual(
|
||||||
|
line,
|
||||||
|
want,
|
||||||
|
`Bad line at stride ${stride}: want: ${want} got: ${line}`
|
||||||
|
);
|
||||||
|
done += line.byteLength;
|
||||||
|
}
|
||||||
|
assertEqual(
|
||||||
|
done,
|
||||||
|
testOutput.byteLength,
|
||||||
|
`readLine didn't return everything: got: ${done}, ` +
|
||||||
|
`want: ${testOutput} (stride: ${stride})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test(async function bufioReadLine() {
|
||||||
|
await testReadLine(testInput);
|
||||||
|
await testReadLine(testInputrn);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue