bufio: bread tops up a partial buffer (Hare short-read)
This commit is contained in:
@@ -192,25 +192,39 @@ export fn isbuffered(s: io.stream) bool = {
|
|||||||
|
|
||||||
// ---- buffered-stream vtable callbacks ------------------------------------
|
// ---- buffered-stream vtable callbacks ------------------------------------
|
||||||
|
|
||||||
// bread — buffered read. Recover stream via the intrusive cast; refill
|
// bread — buffered read. Recover stream via the intrusive cast; top up
|
||||||
// rbuf from src via io.read on empty pending region. Mirrors
|
// rbuf from src via io.read whenever the pending region holds fewer
|
||||||
// ref/hare/bufio/stream.ha (stream_read).
|
// bytes than requested AND rbuf has spare capacity (Hare's short-read
|
||||||
|
// refill, ref/hare/bufio/stream.ha:209). The pending bytes shift to the
|
||||||
|
// front, then io.read fills the tail; EOF is fatal only when nothing is
|
||||||
|
// pending. Mirrors ref/hare/bufio/stream.ha:205 (stream_read).
|
||||||
fn bread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
fn bread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||||
let b: *stream = s: *stream;
|
let b: *stream = s: *stream;
|
||||||
|
// Degenerate read-disabled mode (zero-length rbuf, bufio.ww:87-89);
|
||||||
|
// ww's contract returns eof where Hare (always given a buffer) would
|
||||||
|
// serve 0 — a separate concern from the top-up below.
|
||||||
if (b.rbuf.len == 0) {
|
if (b.rbuf.len == 0) {
|
||||||
let e: io.eof; return e;
|
let e: io.eof; return e;
|
||||||
};
|
};
|
||||||
if (b.rstart >= b.rend) {
|
let avail: i32 = b.rend - b.rstart;
|
||||||
|
if (avail < buf.len && avail < b.rbuf.len) {
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < avail) {
|
||||||
|
b.rbuf[i] = b.rbuf[b.rstart + i];
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
b.rstart = 0;
|
b.rstart = 0;
|
||||||
b.rend = 0;
|
b.rend = avail;
|
||||||
let r: (size | io.eof | io.error) = io.read(b.src, b.rbuf);
|
let r: (size | io.eof | io.error) = io.read(b.src, b.rbuf[b.rend:b.rbuf.len]);
|
||||||
match (r) {
|
match (r) {
|
||||||
case let n: size => { b.rend = n: i32; };
|
case let n: size => { b.rend += n: i32; };
|
||||||
case io.eof => { let e: io.eof; return e; };
|
case io.eof => {
|
||||||
|
if (avail == 0) { let e: io.eof; return e; };
|
||||||
|
};
|
||||||
case let e: io.error => return e;
|
case let e: io.error => return e;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
let avail: i32 = b.rend - b.rstart;
|
avail = b.rend - b.rstart;
|
||||||
let n: i32 = buf.len;
|
let n: i32 = buf.len;
|
||||||
if (avail < n) { n = avail; };
|
if (avail < n) { n = avail; };
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -511,6 +511,89 @@ fn errsource() io.stream = {
|
|||||||
assert(!(bufio.isbuffered(m)));
|
assert(!(bufio.isbuffered(m)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- bread tops up a partial pending buffer (Hare short-read) --------
|
||||||
|
//
|
||||||
|
// rbuf holds SOME pending bytes (fewer than requested) AND has spare
|
||||||
|
// capacity: Hare shifts the pending region to the front and reads more
|
||||||
|
// into the tail before serving (ref/hare/bufio/stream.ha:209). Pre-fix
|
||||||
|
// ww served only the short pending count and never topped up.
|
||||||
|
@test fn streamreadtopup() void = {
|
||||||
|
let raw: [16]u8;
|
||||||
|
let n: i32 = putstr("ABCDEFGHIJKLMNOP", raw[0:16], 0);
|
||||||
|
|
||||||
|
let mem: memio.stream = memio.fixed(raw[0:n]);
|
||||||
|
let m: io.stream = &mem.vt;
|
||||||
|
|
||||||
|
let rb: [8]u8;
|
||||||
|
let wb: [4]u8;
|
||||||
|
let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:4]);
|
||||||
|
|
||||||
|
let p: io.stream = &b.vt;
|
||||||
|
|
||||||
|
// First read fills rbuf (8B "ABCDEFGH"), serves 3 — 5 pending remain.
|
||||||
|
let out: [10]u8;
|
||||||
|
let r1: (size | io.eof | io.error) = io.read(p, out[0:3]);
|
||||||
|
match (r1) {
|
||||||
|
case let z: size => { assert(!(z: i32 != 3)); };
|
||||||
|
case io.eof => abort();
|
||||||
|
case let e: io.error => abort();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Second read wants 10 > 5 pending: Hare shifts "DEFGH" to the front,
|
||||||
|
// reads 3 more ("IJK") into the tail, serves 8. Pre-fix ww returned 5.
|
||||||
|
let r2: (size | io.eof | io.error) = io.read(p, out[0:10]);
|
||||||
|
match (r2) {
|
||||||
|
case let z: size => { assert(!(z: i32 != 8)); };
|
||||||
|
case io.eof => abort();
|
||||||
|
case let e: io.error => abort();
|
||||||
|
};
|
||||||
|
assert(!(out[0] != 68u8)); // 'D'
|
||||||
|
assert(!(out[7] != 75u8)); // 'K'
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---- bread serves pending bytes when the top-up read hits EOF --------
|
||||||
|
//
|
||||||
|
// The load-bearing "EOF fatal only when avail==0" path
|
||||||
|
// (ref/hare/bufio/stream.ha:216): rbuf still holds pending bytes when
|
||||||
|
// the top-up io.read returns EOF — Hare drains the pending region, it
|
||||||
|
// does NOT propagate eof. Source is shorter than two reads so the
|
||||||
|
// second read's top-up io.read EOFs with bytes still buffered.
|
||||||
|
@test fn streamreadtopupeof() void = {
|
||||||
|
let raw: [5]u8;
|
||||||
|
let n: i32 = putstr("ABCDE", raw[0:5], 0);
|
||||||
|
|
||||||
|
let mem: memio.stream = memio.fixed(raw[0:n]);
|
||||||
|
let m: io.stream = &mem.vt;
|
||||||
|
|
||||||
|
let rb: [8]u8;
|
||||||
|
let wb: [4]u8;
|
||||||
|
let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:4]);
|
||||||
|
|
||||||
|
let p: io.stream = &b.vt;
|
||||||
|
|
||||||
|
// First read fills rbuf (5B "ABCDE", source drained), serves 3 —
|
||||||
|
// 2 pending remain.
|
||||||
|
let out: [10]u8;
|
||||||
|
let r1: (size | io.eof | io.error) = io.read(p, out[0:3]);
|
||||||
|
match (r1) {
|
||||||
|
case let z: size => { assert(!(z: i32 != 3)); };
|
||||||
|
case io.eof => abort();
|
||||||
|
case let e: io.error => abort();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Second read wants 10 > 2 pending: shift "DE" to the front, top-up
|
||||||
|
// io.read hits EOF (source drained). avail==2 != 0 so Hare drains the
|
||||||
|
// pending region — serves 2, NOT eof. "always fatal" would return eof.
|
||||||
|
let r2: (size | io.eof | io.error) = io.read(p, out[0:10]);
|
||||||
|
match (r2) {
|
||||||
|
case let z: size => { assert(!(z: i32 != 2)); };
|
||||||
|
case io.eof => abort();
|
||||||
|
case let e: io.error => abort();
|
||||||
|
};
|
||||||
|
assert(!(out[0] != 68u8)); // 'D'
|
||||||
|
assert(!(out[1] != 69u8)); // 'E'
|
||||||
|
};
|
||||||
|
|
||||||
// ---- bread + unread: pushed-back bytes come out first ----------------
|
// ---- bread + unread: pushed-back bytes come out first ----------------
|
||||||
|
|
||||||
@test fn streamunread() void = {
|
@test fn streamunread() void = {
|
||||||
|
|||||||
Reference in New Issue
Block a user