lib+test/wcc: add memio, rename io stream.ww→io.ww
Mirrors Hare's memio: fixed, dynamic, dynamicfrom, buffer, string, reset, borrowedread. Caller owns the state + stream slots because w6c lacks &x.field and 32B return-by-value. Tests table-driven via parallel arrays. io.stream now exported.
This commit is contained in:
@@ -13,7 +13,7 @@ export type eof = void;
|
||||
// closed. NAMED-void; same shape, different tag.
|
||||
export type closed = void;
|
||||
|
||||
type stream = struct {
|
||||
export type stream = struct {
|
||||
ctx: *void,
|
||||
read: fn(s: *stream, buf: []u8) (i32 | eof | closed),
|
||||
write: fn(s: *stream, buf: []u8) (i32 | closed),
|
||||
212
lib/memio/memio.ww
Normal file
212
lib/memio/memio.ww
Normal file
@@ -0,0 +1,212 @@
|
||||
// memio — in-memory io stream.
|
||||
//
|
||||
// Hare's memio:: surface, drop underscores. Two flavours behind a
|
||||
// single [[io.stream]]:
|
||||
//
|
||||
// fixed caller owns the buffer, writes stop when full.
|
||||
// dynamic memio owns the buffer, writes grow it; close frees.
|
||||
//
|
||||
// Call shape divergence from Hare: the caller supplies both the
|
||||
// memio `state` and the `io.stream` slot, by pointer. ww cgen does
|
||||
// not yet implement &x.field or 32B-struct return-by-value, so the
|
||||
// Hare `let s = memio::fixed(buf)` shape isn't reachable; collapse
|
||||
// to a single returned struct when those land (lib/CLAUDE.md
|
||||
// "graduate in one go").
|
||||
//
|
||||
// let mem: memio.state;
|
||||
// let s: io.stream;
|
||||
// memio.fixed(&mem, &s, buf);
|
||||
// io.write(&s, bytes);
|
||||
//
|
||||
// Subset of Hare's surface: io.stream's variants are {eof, closed},
|
||||
// so memio drops Hare's NONBLOCK flag (would need an `again` variant
|
||||
// in lib/io) and string()'s utf8 validation (lib has no fromutf8
|
||||
// today). Hare's seek / copy callbacks are likewise absent: lib/io's
|
||||
// stream vtable has only read/write/close slots, so memio can't wire
|
||||
// a seeker or copier even if we wanted to. All three come back when
|
||||
// their dependencies do.
|
||||
|
||||
use io;
|
||||
|
||||
// Direct rt_alloc/rt_free bindings rather than `use os;` because os
|
||||
// exports read/write/close, which collide with io.read/write/close
|
||||
// under the driver's flat-scope concatenation. Same pattern as
|
||||
// rt/ensure.ww.
|
||||
@symbol("rt_alloc") fn rtalloc(n: u64) *void;
|
||||
@symbol("rt_free") fn rtfree(p: *void, n: u64) void;
|
||||
|
||||
// state — memio's per-stream bookkeeping. The caller owns the slot
|
||||
// and passes its address into a constructor. `ptr/len/cap` are the
|
||||
// slice fields kept flat to dodge a chained-dot write through the
|
||||
// state pointer (cgen doesn't store into `m.buf.ptr` reliably).
|
||||
export type state = struct {
|
||||
ptr: *u8,
|
||||
len: i32,
|
||||
cap: i32,
|
||||
pos: i32,
|
||||
};
|
||||
|
||||
// fixed — wire `s` over a caller-supplied buffer. Writes never grow;
|
||||
// they return 0 once `pos` reaches the end of the buffer.
|
||||
export fn fixed(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.len;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = fixedwrite;
|
||||
s.close = closenoop;
|
||||
};
|
||||
|
||||
// dynamic — wire `s` with no initial buffer. Writes grow the backing
|
||||
// allocation; [[io.close]] frees it.
|
||||
export fn dynamic(m: *state, s: *io.stream) void = {
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
};
|
||||
|
||||
// dynamicfrom — like [[dynamic]] but seeded with an existing slice.
|
||||
// Ownership of the slice transfers to the stream; [[io.close]] frees
|
||||
// it. The slice must come from the runtime allocator: close calls
|
||||
// rt_free with `m.cap` bytes, which is taken from `buf.cap` (the
|
||||
// slice's allocated capacity), not its logical length. Passing a
|
||||
// half-filled append slice (len < cap) and using only `buf.len` here
|
||||
// would under-free on close.
|
||||
export fn dynamicfrom(m: *state, s: *io.stream, buf: []u8) void = {
|
||||
m.ptr = buf.ptr;
|
||||
m.len = buf.len;
|
||||
m.cap = buf.cap;
|
||||
m.pos = 0;
|
||||
s.ctx = m: *void;
|
||||
s.read = readfn;
|
||||
s.write = dynamicwrite;
|
||||
s.close = dynamicclose;
|
||||
};
|
||||
|
||||
// buffer — borrowed view of bytes written so far (buf[..pos]).
|
||||
// Seek to the end before calling if the full buffer is wanted.
|
||||
export fn buffer(m: *state) []u8 = {
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// string — bytes written so far, as a str view. Hare returns
|
||||
// (str | utf8::invalid); ww doesn't ship utf8 validation yet, so
|
||||
// this returns the unchecked view.
|
||||
export fn string(m: *state) str = {
|
||||
let r: str;
|
||||
r.ptr = m.ptr;
|
||||
r.len = m.pos;
|
||||
return r;
|
||||
};
|
||||
|
||||
// reset — rewind the cursor and truncate the logical content to 0.
|
||||
// Backing storage is preserved; subsequent writes (dynamic) re-fill
|
||||
// from the start without reallocation.
|
||||
export fn reset(m: *state) void = {
|
||||
m.pos = 0;
|
||||
m.len = 0;
|
||||
};
|
||||
|
||||
// borrowedread — return an `amt`-byte view starting at `pos` without
|
||||
// copying, advancing the cursor. eof if fewer bytes are available.
|
||||
export fn borrowedread(m: *state, amt: i32) ([]u8 | io.eof) = {
|
||||
if (m.len - m.pos < amt) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let r: []u8;
|
||||
r.ptr = m.ptr + (m.pos: u64);
|
||||
r.len = amt;
|
||||
m.pos += amt;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ---- vtable callbacks ------------------------------------------------
|
||||
|
||||
fn readfn(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.pos >= m.len) {
|
||||
let e: io.eof;
|
||||
return e;
|
||||
};
|
||||
let avail: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (avail < n) { n = avail; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
buf[i] = m.ptr[m.pos + i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn fixedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.pos >= m.len) { return 0; };
|
||||
let space: i32 = m.len - m.pos;
|
||||
let n: i32 = buf.len;
|
||||
if (space < n) { n = space; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += n;
|
||||
return n;
|
||||
};
|
||||
|
||||
fn dynamicwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
let need: i32 = m.pos + buf.len;
|
||||
if (need > m.cap) { grow(m, need); };
|
||||
let i: i32 = 0;
|
||||
for (i < buf.len) {
|
||||
m.ptr[m.pos + i] = buf[i];
|
||||
i += 1;
|
||||
};
|
||||
m.pos += buf.len;
|
||||
if (m.pos > m.len) { m.len = m.pos; };
|
||||
return buf.len;
|
||||
};
|
||||
|
||||
fn dynamicclose(s: *io.stream) (void | io.closed) = {
|
||||
let m: *state = s.ctx: *state;
|
||||
if (m.cap > 0) { rtfree(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nil;
|
||||
m.len = 0;
|
||||
m.cap = 0;
|
||||
m.pos = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn closenoop(s: *io.stream) (void | io.closed) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Double-and-copy growth. Initial bump from 0 lands at 8 to amortise
|
||||
// small write bursts without a tail of reallocs.
|
||||
fn grow(m: *state, need: i32) void = {
|
||||
let newcap: i32 = m.cap;
|
||||
if (newcap < 8) { newcap = 8; };
|
||||
for (newcap < need) { newcap *= 2; };
|
||||
let nbuf: *u8 = rtalloc(newcap: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < m.len) {
|
||||
nbuf[i] = m.ptr[i];
|
||||
i += 1;
|
||||
};
|
||||
if (m.cap > 0) { rtfree(m.ptr: *void, m.cap: u64); };
|
||||
m.ptr = nbuf;
|
||||
m.cap = newcap;
|
||||
};
|
||||
366
lib/memio/memiotest.ww
Normal file
366
lib/memio/memiotest.ww
Normal file
@@ -0,0 +1,366 @@
|
||||
// memiotest — exercises lib/memio. Run with `out/bin/ww run lib/memio/memiotest.ww`.
|
||||
//
|
||||
// Every @test enumerates parallel `[N]T` arrays of inputs and
|
||||
// expectations, then iterates one body across them. Parallel arrays
|
||||
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
|
||||
// `arr[i].field` store gap (task #6).
|
||||
|
||||
use bytes;
|
||||
use io;
|
||||
use memio;
|
||||
|
||||
// Direct exit(2) binding rather than `use os;` — os exports
|
||||
// read/write/close, which collide with io.read/write/close under
|
||||
// the driver's flat-scope concat (task #7).
|
||||
@symbol("rt_syscall") fn syscall1ww(num: i64, a: i64) i64;
|
||||
fn doexit(code: i32) void = {
|
||||
syscall1ww(60i64, code: i64);
|
||||
};
|
||||
|
||||
// signalled — bumped by main before each test so a failing exit code
|
||||
// pinpoints the offending case.
|
||||
let signalled: i32 = 0;
|
||||
|
||||
fn fail() void = { doexit(signalled + 10); };
|
||||
|
||||
fn putstr(s: str, into: []u8, off: i32) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
into[off + i] = s[i];
|
||||
i += 1;
|
||||
};
|
||||
return off + s.len;
|
||||
};
|
||||
|
||||
// ---- fixedread: read sizes drive partial / full / eof outcomes ----------
|
||||
|
||||
@test fn fixedread() void = {
|
||||
let arr: [8]u8;
|
||||
arr[0] = 1u8; arr[1] = 2u8; arr[2] = 3u8; arr[3] = 4u8;
|
||||
arr[4] = 5u8; arr[5] = 6u8; arr[6] = 7u8; arr[7] = 8u8;
|
||||
|
||||
// (request, wantn, wantfirst, wantlast, weof)
|
||||
// 5 rows: under-remaining, full slot, last-byte-only, post-end, 0-len-at-end.
|
||||
let req: [5]i32;
|
||||
let wantn: [5]i32;
|
||||
let wantf: [5]u8;
|
||||
let wantl: [5]u8;
|
||||
let weof: [5]i32;
|
||||
req[0]=3; wantn[0]=3; wantf[0]=1u8; wantl[0]=3u8; weof[0]=0;
|
||||
req[1]=4; wantn[1]=4; wantf[1]=4u8; wantl[1]=7u8; weof[1]=0;
|
||||
req[2]=5; wantn[2]=1; wantf[2]=8u8; wantl[2]=8u8; weof[2]=0;
|
||||
req[3]=1; wantn[3]=0; wantf[3]=0u8; wantl[3]=0u8; weof[3]=1;
|
||||
req[4]=0; wantn[4]=0; wantf[4]=0u8; wantl[4]=0u8; weof[4]=1;
|
||||
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.fixed(&mem, &s, arr[0:8]);
|
||||
|
||||
let out: [8]u8;
|
||||
let i: i32 = 0;
|
||||
for (i < 5) {
|
||||
// Clear out so spot checks read real data, not stale bytes.
|
||||
let k: i32 = 0;
|
||||
for (k < 8) { out[k] = 0u8; k += 1; };
|
||||
|
||||
let r: (i32 | io.eof | io.closed) = io.read(&s, out[0:req[i]]);
|
||||
match (r) {
|
||||
case let n: i32 => {
|
||||
if (weof[i] != 0) { fail(); };
|
||||
if (n != wantn[i]) { fail(); };
|
||||
if (n > 0 && out[0] != wantf[i]) { fail(); };
|
||||
if (n > 0 && out[n - 1] != wantl[i]) { fail(); };
|
||||
};
|
||||
case io.eof => { if (weof[i] == 0) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// closenoop on a fixed stream returns void.
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fixedwritecases: full-fit / exact-fill / partial / overflow -------------
|
||||
|
||||
@test fn fixedwritecases() void = {
|
||||
let dst: [16]u8;
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.fixed(&mem, &s, dst[0:16]);
|
||||
|
||||
// One flat source; rows index into it via (off, len). Sequence:
|
||||
// "hello " (6), "world!!" (7), "XXXXXXX" (7), "Y" (1).
|
||||
let src: [32]u8;
|
||||
let _: i32 = putstr("hello world!!XXXXXXXY", src[0:32], 0);
|
||||
|
||||
// (srcoff, inlen, wantn) — wantn diverges from inlen on the
|
||||
// partial and overflow rows.
|
||||
let off: [4]i32;
|
||||
let ln: [4]i32;
|
||||
let wantn: [4]i32;
|
||||
off[0]=0; ln[0]=6; wantn[0]=6; // full fit
|
||||
off[1]=6; ln[1]=7; wantn[1]=7; // exact-fills cap (pos=13)
|
||||
off[2]=13; ln[2]=7; wantn[2]=3; // partial: 3 free
|
||||
off[3]=20; ln[3]=1; wantn[3]=0; // overflow: 0 free → 0
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < 4) {
|
||||
let lo: i32 = off[i];
|
||||
let hi: i32 = lo + ln[i];
|
||||
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != wantn[i]) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
|
||||
let want: [16]u8;
|
||||
let _: i32 = putstr("hello world!!XXX", want[0:16], 0);
|
||||
if (!bytes.equal(dst[0:16], want[0:16])) { fail(); };
|
||||
|
||||
// 0-byte write is always 0 with no side effects.
|
||||
let r0: (i32 | io.closed) = io.write(&s, src[0:0]);
|
||||
match (r0) {
|
||||
case let n: i32 => { if (n != 0) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- dynamicgrow: every cap doubling exercised --------------------------
|
||||
|
||||
// Drive grow 0 → 8 → 16 → 32 by writing sized chunks. Verify
|
||||
// accumulated `pos` after each step.
|
||||
@test fn dynamicgrow() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let src: [32]u8;
|
||||
let _: i32 = putstr("abcdefghijklmnopqrstuvwxyz012345", src[0:32], 0);
|
||||
|
||||
// (chunkn, totaln) — cap transitions: 0→8 at row 0; 8→16 at row 2;
|
||||
// 16→32 at row 4.
|
||||
let chunk: [5]i32;
|
||||
let total: [5]i32;
|
||||
chunk[0]=3; total[0]=3;
|
||||
chunk[1]=5; total[1]=8;
|
||||
chunk[2]=1; total[2]=9;
|
||||
chunk[3]=7; total[3]=16;
|
||||
chunk[4]=10; total[4]=26;
|
||||
|
||||
let off: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < 5) {
|
||||
let lo: i32 = off;
|
||||
let hi: i32 = lo + chunk[i];
|
||||
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != chunk[i]) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (memio.buffer(&mem).len != total[i]) { fail(); };
|
||||
off += chunk[i];
|
||||
i += 1;
|
||||
};
|
||||
|
||||
if (!bytes.equal(memio.buffer(&mem), src[0:26])) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- dynamicreset: write / reset / write cycles -------------------------
|
||||
|
||||
// op=0 writes `ln` bytes from a rolling source; op=1 resets and ignores
|
||||
// ln. After each row the accumulated len must equal `want`.
|
||||
@test fn dynamicreset() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let src: [16]u8;
|
||||
src[0]=10u8; src[1]=20u8; src[2]=30u8; src[3]=40u8;
|
||||
src[4]=50u8; src[5]=60u8; src[6]=70u8; src[7]=80u8;
|
||||
|
||||
let op: [6]i32;
|
||||
let ln: [6]i32;
|
||||
let want: [6]i32;
|
||||
op[0]=0; ln[0]=5; want[0]=5; // initial write
|
||||
op[1]=1; ln[1]=0; want[1]=0; // reset (after writes)
|
||||
op[2]=0; ln[2]=3; want[2]=3; // refill from 0
|
||||
op[3]=0; ln[3]=2; want[3]=5; // continue
|
||||
op[4]=1; ln[4]=0; want[4]=0; // reset (after partial)
|
||||
op[5]=0; ln[5]=8; want[5]=8; // larger refill
|
||||
|
||||
let off: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < 6) {
|
||||
if (op[i] == 1) {
|
||||
memio.reset(&mem);
|
||||
off = 0;
|
||||
} else {
|
||||
let lo: i32 = off;
|
||||
let hi: i32 = lo + ln[i];
|
||||
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != ln[i]) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
off += ln[i];
|
||||
};
|
||||
if (memio.buffer(&mem).len != want[i]) { fail(); };
|
||||
i += 1;
|
||||
};
|
||||
|
||||
if (!bytes.equal(memio.buffer(&mem), src[0:8])) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- borrowedread: under / exact / over / 0-byte ------------------------
|
||||
|
||||
@test fn borrowedreadcases() void = {
|
||||
let arr: [6]u8;
|
||||
arr[0]=0u8; arr[1]=1u8; arr[2]=2u8; arr[3]=3u8; arr[4]=4u8; arr[5]=5u8;
|
||||
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.fixed(&mem, &s, arr[0:6]);
|
||||
|
||||
// (amt, wantfirst, wantlast, weof)
|
||||
let amt: [4]i32;
|
||||
let wf: [4]u8;
|
||||
let wl: [4]u8;
|
||||
let weof: [4]i32;
|
||||
amt[0]=4; wf[0]=0u8; wl[0]=3u8; weof[0]=0; // under remaining
|
||||
amt[1]=2; wf[1]=4u8; wl[1]=5u8; weof[1]=0; // exact remaining
|
||||
amt[2]=1; wf[2]=0u8; wl[2]=0u8; weof[2]=1; // past end → eof
|
||||
amt[3]=0; wf[3]=0u8; wl[3]=0u8; weof[3]=0; // 0-byte view always ok
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < 4) {
|
||||
let r: ([]u8 | io.eof) = memio.borrowedread(&mem, amt[i]);
|
||||
match (r) {
|
||||
case let v: []u8 => {
|
||||
if (weof[i] != 0) { fail(); };
|
||||
if (v.len != amt[i]) { fail(); };
|
||||
if (v.len > 0 && v[0] != wf[i]) { fail(); };
|
||||
if (v.len > 0 && v[v.len - 1] != wl[i]) { fail(); };
|
||||
};
|
||||
case io.eof => { if (weof[i] == 0) { fail(); }; };
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// ---- stringview: len + endpoints track pos across appends ---------------
|
||||
|
||||
@test fn stringview() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
if (memio.string(&mem).len != 0) { fail(); };
|
||||
|
||||
let src: [32]u8;
|
||||
let _: i32 = putstr("hello world!!", src[0:32], 0);
|
||||
|
||||
// (srcoff, inlen, wantacc, wantfirst, wantlast)
|
||||
let off: [3]i32;
|
||||
let ln: [3]i32;
|
||||
let want: [3]i32;
|
||||
let wf: [3]u8;
|
||||
let wl: [3]u8;
|
||||
off[0]=0; ln[0]=5; want[0]=5; wf[0]=104u8; wl[0]=111u8; // "hello" h..o
|
||||
off[1]=5; ln[1]=6; want[1]=11; wf[1]=104u8; wl[1]=100u8; // +" world" h..d
|
||||
off[2]=11; ln[2]=2; want[2]=13; wf[2]=104u8; wl[2]=33u8; // +"!!" h..!
|
||||
|
||||
let i: i32 = 0;
|
||||
for (i < 3) {
|
||||
let lo: i32 = off[i];
|
||||
let hi: i32 = lo + ln[i];
|
||||
let r: (i32 | io.closed) = io.write(&s, src[lo:hi]);
|
||||
match (r) { case let n: i32 => {}; case io.closed => fail(); };
|
||||
let v: str = memio.string(&mem);
|
||||
if (v.len != want[i]) { fail(); };
|
||||
if (v[0] != wf[i]) { fail(); };
|
||||
if (v[v.len - 1] != wl[i]) { fail(); };
|
||||
i += 1;
|
||||
};
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- dynamicfromseed: ownership-transferred seed, read then write -------
|
||||
|
||||
@test fn dynamicfromseed() void = {
|
||||
// Build a heap-allocated seed via append (rt_ensure path), then
|
||||
// hand ownership to memio. dynamicclose then frees `seed.cap`
|
||||
// bytes — the regression that motivated the `m.cap = buf.cap`
|
||||
// fix in lib/memio/memio.ww.
|
||||
let seed: []u8;
|
||||
seed.ptr = nil; seed.len = 0; seed.cap = 0;
|
||||
append(seed, 100u8, 101u8, 102u8, 103u8);
|
||||
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamicfrom(&mem, &s, seed);
|
||||
|
||||
// (readn, wantbyte, weof)
|
||||
let rn: [5]i32;
|
||||
let want: [5]u8;
|
||||
let weof: [5]i32;
|
||||
rn[0]=1; want[0]=100u8; weof[0]=0;
|
||||
rn[1]=1; want[1]=101u8; weof[1]=0;
|
||||
rn[2]=1; want[2]=102u8; weof[2]=0;
|
||||
rn[3]=1; want[3]=103u8; weof[3]=0;
|
||||
rn[4]=1; want[4]=0u8; weof[4]=1; // past seed end → eof
|
||||
|
||||
let out: [4]u8;
|
||||
let i: i32 = 0;
|
||||
for (i < 5) {
|
||||
let r: (i32 | io.eof | io.closed) = io.read(&s, out[0:rn[i]]);
|
||||
match (r) {
|
||||
case let n: i32 => {
|
||||
if (weof[i] != 0) { fail(); };
|
||||
if (n != rn[i]) { fail(); };
|
||||
if (out[0] != want[i]) { fail(); };
|
||||
};
|
||||
case io.eof => { if (weof[i] == 0) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
|
||||
// Now write extends past the seed; grow path runs and close
|
||||
// must free the (post-grow) cap, not the seed cap.
|
||||
let extra: [4]u8;
|
||||
extra[0]=200u8; extra[1]=201u8; extra[2]=202u8; extra[3]=203u8;
|
||||
let w: (i32 | io.closed) = io.write(&s, extra[0:4]);
|
||||
match (w) {
|
||||
case let n: i32 => { if (n != 4) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; fixedread();
|
||||
signalled = 2; fixedwritecases();
|
||||
signalled = 3; dynamicgrow();
|
||||
signalled = 4; dynamicreset();
|
||||
signalled = 5; borrowedreadcases();
|
||||
signalled = 6; stringview();
|
||||
signalled = 7; dynamicfromseed();
|
||||
return 0;
|
||||
};
|
||||
@@ -14,7 +14,7 @@ static const char *modules[] = {
|
||||
"lib/ascii/ascii.ww",
|
||||
"lib/bytes/bytes.ww",
|
||||
"lib/strings/strings.ww",
|
||||
"lib/io/stream.ww",
|
||||
"lib/io/io.ww",
|
||||
"lib/errors/errors.ww",
|
||||
"lib/os/os.ww",
|
||||
"lib/strconv/strconv.ww",
|
||||
|
||||
@@ -204,7 +204,7 @@ probe_dump_diff(const char *bin)
|
||||
"lib/fmt/fmt.ww",
|
||||
"lib/strings/strings.ww",
|
||||
"lib/bytes/bytes.ww",
|
||||
"lib/io/stream.ww",
|
||||
"lib/io/io.ww",
|
||||
"lib/errors/errors.ww",
|
||||
"lib/bufio/bufio.ww",
|
||||
"lib/types/types.ww",
|
||||
|
||||
Reference in New Issue
Block a user