lib: collapse the parallel vstream scaffold onto the single Hare io surface (#94 fold-eFinal)
The Option-C parallel _v vstream API was scaffolding to bring the io stack up alongside the old surface; carrying both permanently is a rule-9 divergence from ref/hare, which has exactly one io surface. Collapse onto that surface (stream = *vtable, ref/hare/io/stream.ha) and rename the _v symbols to their Hare names (io vstream->stream, fmt vfprint->fprint, bufio/memio/log surfaces, log.new). Deletes the 4 lib/*/vstream.ww scaffold files; regenerates w6c/wwdump combined.ww. cstage and wwstage stay byte-identical and combined_ww_fresh holds; all 220 tests pass.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
// shlex.syntaxerr — !void; bad shell syntax
|
||||
// shlex.strerror(syntaxerr) — "Invalid shell syntax"
|
||||
// shlex.split(in: str) — tokenize; returns ([]str | syntaxerr)
|
||||
// shlex.quote(*io.stream, s) — write `s` shell-quoted to a sink
|
||||
// shlex.quote(io.stream, s) — write `s` shell-quoted to a sink
|
||||
// shlex.quotestr(s: str) str — quote into a fresh str
|
||||
//
|
||||
// Owning model: split() returns a fresh `[]str` of strings.dup'd
|
||||
@@ -39,19 +39,16 @@
|
||||
// / scan_backslash work on bytes; an escaped multibyte sequence
|
||||
// round-trips as the byte sequence it appears in the input.
|
||||
//
|
||||
// - memio glue uses ww's caller-supplies-state shape:
|
||||
// let mst: memio.state; let s: io.stream;
|
||||
// memio.dynamic(&mst, &s);
|
||||
// ... io.write(&s, ...) ...
|
||||
// let view: str = memio.string(&mst); // borrowed
|
||||
// instead of Hare's `let s = memio::dynamic()` return-by-value.
|
||||
// Locked in by lib/memio's commit until cgen ships >24B return-
|
||||
// by-value (graduates "in one go" per lib/CLAUDE.md).
|
||||
// - memio glue uses the value-return shape (#94 fold-eFinal):
|
||||
// let st: memio.stream = memio.dynamic();
|
||||
// ... io.write(&st.vt, ...) ...
|
||||
// let view: str = memio.string(&st); // borrowed
|
||||
// mirroring Hare's `let s = memio::dynamic()` return-by-value.
|
||||
// `&st.vt` is the [[io.stream]] (= `*io.vtable`) the dispatchers
|
||||
// take.
|
||||
//
|
||||
// - quote() takes `*io.stream` (not Hare's `io::handle`) — that's
|
||||
// already the ww-divergent shape lib/io ships. Returns
|
||||
// `(i32 | io.closed)` instead of Hare's wider io::error: lib/io's
|
||||
// stream vtable carries only `closed` on the write arm.
|
||||
// - quote() takes `io.stream` (not Hare's `io::handle`) — that's
|
||||
// the ww-divergent shape lib/io ships until io fold-2 (#5).
|
||||
//
|
||||
// - quote() emits raw bytes directly into the sink; without
|
||||
// [[memio.appendrune]] / [[memio.concat]] in lib/memio yet, the
|
||||
@@ -86,11 +83,9 @@
|
||||
// Caller layout — quote into a fixed buffer:
|
||||
//
|
||||
// let buf: [128]u8;
|
||||
// let mst: memio.state;
|
||||
// let s: io.stream;
|
||||
// memio.fixed(&mst, &s, buf[0:128]);
|
||||
// shlex.quote(&s, "hello world"); // writes 'hello world'
|
||||
// let view: str = memio.string(&mst);
|
||||
// let st: memio.stream = memio.fixed(buf[0:128]);
|
||||
// shlex.quote(&st.vt, "hello world"); // writes 'hello world'
|
||||
// let view: str = memio.string(&st);
|
||||
|
||||
package shlex;
|
||||
|
||||
@@ -167,11 +162,11 @@ fn freepartial(slice: []str) void = {
|
||||
// ship an appendbyte/appendrune in the ww port yet, so this is the
|
||||
// uniform write-one-byte primitive across the module.
|
||||
//
|
||||
// The dynamic memio sink never returns `io.closed`; the [[split]] +
|
||||
// [[quotestr]] callers ignore the error arm because their stream is
|
||||
// always memio.dynamic. [[quote]] (caller-supplied sink) propagates
|
||||
// it through its `(i32 | io.closed)` return.
|
||||
fn writebyte(s: *io.stream, b: u8) (i32 | io.closed) = {
|
||||
// The dynamic memio sink never errors; the [[split]] + [[quotestr]]
|
||||
// callers ignore the error arm because their stream is always
|
||||
// memio.dynamic. [[quote]] (caller-supplied sink) propagates it through
|
||||
// its `(size | io.error)` return.
|
||||
fn writebyte(s: io.stream, b: u8) (size | io.error) = {
|
||||
let buf: [1]u8;
|
||||
buf[0] = b;
|
||||
return io.write(s, buf[0:1]);
|
||||
@@ -182,7 +177,7 @@ fn writebyte(s: *io.stream, b: u8) (i32 | io.closed) = {
|
||||
// newline silently. A trailing bare backslash (`*pos == in.len`) is
|
||||
// `syntaxerr`. Otherwise the next byte is appended verbatim. Mirrors
|
||||
// Hare's scan_backslash (split.ha:86).
|
||||
fn scan_backslash(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
fn scan_backslash(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
if (*pos >= in.len) { let e: syntaxerr; return e; };
|
||||
let r: u8 = in[*pos];
|
||||
*pos += 1;
|
||||
@@ -194,7 +189,7 @@ fn scan_backslash(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
// scan_double — consume a `"..."` group at `*pos`. Closes on '"';
|
||||
// '\<c>' inside processed by [[scan_backslash]]; unterminated → err.
|
||||
// Mirrors Hare's scan_double (split.ha:110).
|
||||
fn scan_double(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
fn scan_double(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
let loop: bool = true;
|
||||
for (loop) {
|
||||
if (*pos >= in.len) { let e: syntaxerr; return e; };
|
||||
@@ -217,7 +212,7 @@ fn scan_double(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
// scan_single — consume a `'...'` group at `*pos`. Closes on "'";
|
||||
// no escapes inside (POSIX: single-quoted text is fully literal);
|
||||
// unterminated → err. Mirrors Hare's scan_single (split.ha:135).
|
||||
fn scan_single(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
fn scan_single(out: io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
let loop: bool = true;
|
||||
for (loop) {
|
||||
if (*pos >= in.len) { let e: syntaxerr; return e; };
|
||||
@@ -242,9 +237,8 @@ fn scan_single(out: *io.stream, in: str, pos: *i32) (void | syntaxerr) = {
|
||||
// signal for "an empty quote group was the only content of this
|
||||
// token", which still pushes a literal "" (e.g. `cmd ''`).
|
||||
export fn split(in: str) ([]str | syntaxerr) = {
|
||||
let mst: memio.state;
|
||||
let snk: io.stream;
|
||||
memio.dynamic(&mst, &snk);
|
||||
let st: memio.stream = memio.dynamic();
|
||||
let snk: io.stream = &st.vt;
|
||||
|
||||
let slice: []str;
|
||||
slice.ptr = nil: *str;
|
||||
@@ -275,56 +269,56 @@ export fn split(in: str) ([]str | syntaxerr) = {
|
||||
};
|
||||
};
|
||||
if (!first) {
|
||||
let view: str = memio.string(&mst);
|
||||
let view: str = memio.string(&st);
|
||||
let owned: str = dupstr(view);
|
||||
appendstr(&slice, owned);
|
||||
memio.reset(&mst);
|
||||
memio.reset(&st);
|
||||
};
|
||||
dirty = false;
|
||||
} else { if (r == 92u8) { // '\\'
|
||||
let er = scan_backslash(&snk, in, &pos);
|
||||
let er = scan_backslash(snk, in, &pos);
|
||||
match (er) {
|
||||
case let e: syntaxerr => {
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
freepartial(slice);
|
||||
return e;
|
||||
};
|
||||
case void => {};
|
||||
};
|
||||
} else { if (r == 34u8) { // '"'
|
||||
let er = scan_double(&snk, in, &pos);
|
||||
let er = scan_double(snk, in, &pos);
|
||||
match (er) {
|
||||
case let e: syntaxerr => {
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
freepartial(slice);
|
||||
return e;
|
||||
};
|
||||
case void => {};
|
||||
};
|
||||
} else { if (r == 39u8) { // "'"
|
||||
let er = scan_single(&snk, in, &pos);
|
||||
let er = scan_single(snk, in, &pos);
|
||||
match (er) {
|
||||
case let e: syntaxerr => {
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
freepartial(slice);
|
||||
return e;
|
||||
};
|
||||
case void => {};
|
||||
};
|
||||
} else {
|
||||
let _w = writebyte(&snk, r);
|
||||
let _w = writebyte(snk, r);
|
||||
}; }; }; };
|
||||
|
||||
if (first) { first = false; };
|
||||
};
|
||||
|
||||
if (dirty) {
|
||||
let view: str = memio.string(&mst);
|
||||
let view: str = memio.string(&st);
|
||||
let owned: str = dupstr(view);
|
||||
appendstr(&slice, owned);
|
||||
};
|
||||
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
return slice;
|
||||
};
|
||||
|
||||
@@ -355,10 +349,8 @@ fn issafe(s: str) bool = {
|
||||
};
|
||||
|
||||
// quote — emit `s` shell-quoted to `sink`. Returns total bytes written,
|
||||
// or `io.closed` if the sink rejects mid-write. Mirrors Hare's
|
||||
// shlex::quote (escape.ha:25), modulo the narrower `io.closed`-only
|
||||
// error set on lib/io's stream vtable (Hare's `io::error` would carry
|
||||
// underread/etc., which lib/io doesn't yet model).
|
||||
// or `io.error` if the sink rejects mid-write. Mirrors Hare's
|
||||
// shlex::quote (escape.ha:25).
|
||||
//
|
||||
// Strategy:
|
||||
// - empty `s` → `''`
|
||||
@@ -366,14 +358,14 @@ fn issafe(s: str) bool = {
|
||||
// - otherwise → wrap in single quotes; an embedded `'`
|
||||
// becomes `'"'"'` (close, double-quoted
|
||||
// literal `'`, reopen).
|
||||
export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
export fn quote(sink: io.stream, s: str) (size | io.error) = {
|
||||
if (s.len == 0) {
|
||||
let buf: [2]u8;
|
||||
buf[0] = 39u8; buf[1] = 39u8;
|
||||
let r = io.write(sink, buf[0:2]);
|
||||
match (r) {
|
||||
case let n: i32 => return n;
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => return n;
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
if (issafe(s)) {
|
||||
@@ -382,16 +374,16 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
raw.len = s.len;
|
||||
let r = io.write(sink, raw);
|
||||
match (r) {
|
||||
case let n: i32 => return n;
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => return n;
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
};
|
||||
|
||||
let total: i32 = 0;
|
||||
let total: size = 0;
|
||||
let r1 = writebyte(sink, 39u8); // "'"
|
||||
match (r1) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
@@ -402,22 +394,22 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
pat[3] = 34u8; pat[4] = 39u8;
|
||||
let rr = io.write(sink, pat[0:5]);
|
||||
match (rr) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c2: io.closed => return c2;
|
||||
case let n: size => { total += n; };
|
||||
case let e2: io.error => return e2;
|
||||
};
|
||||
} else {
|
||||
let rr = writebyte(sink, c);
|
||||
match (rr) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c2: io.closed => return c2;
|
||||
case let n: size => { total += n; };
|
||||
case let e2: io.error => return e2;
|
||||
};
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
let r2 = writebyte(sink, 39u8); // "'"
|
||||
match (r2) {
|
||||
case let n: i32 => { total += n; };
|
||||
case let c: io.closed => return c;
|
||||
case let n: size => { total += n; };
|
||||
case let e: io.error => return e;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
@@ -427,19 +419,18 @@ export fn quote(sink: *io.stream, s: str) (i32 | io.closed) = {
|
||||
// `os.free(r.ptr, r.len: u64)`. Mirrors Hare's shlex::quotestr
|
||||
// (escape.ha:50).
|
||||
//
|
||||
// memio.dynamic's writes never return io.closed, so the `match` on
|
||||
// `quote(...)` discards the error arm — it can't reach this caller's
|
||||
// sink. quote()'s `(i32 | io.closed)` shape is preserved for the
|
||||
// caller-supplied-sink path.
|
||||
// memio.dynamic's writes never error, so the `match` on `quote(...)`
|
||||
// discards the error arm — it can't reach this caller's sink. quote()'s
|
||||
// `(size | io.error)` shape is preserved for the caller-supplied-sink
|
||||
// path.
|
||||
export fn quotestr(s: str) str = {
|
||||
let mst: memio.state;
|
||||
let snk: io.stream;
|
||||
memio.dynamic(&mst, &snk);
|
||||
let st: memio.stream = memio.dynamic();
|
||||
let snk: io.stream = &st.vt;
|
||||
|
||||
let _r = quote(&snk, s);
|
||||
let view: str = memio.string(&mst);
|
||||
let _r = quote(snk, s);
|
||||
let view: str = memio.string(&st);
|
||||
let owned: str = dupstr(view);
|
||||
|
||||
let _c = io.close(&snk);
|
||||
let _c = io.close(snk);
|
||||
return owned;
|
||||
};
|
||||
|
||||
@@ -108,22 +108,21 @@ fn checkempty(in: str) void = {
|
||||
};
|
||||
|
||||
fn checkquote(in: str, expected: str) void = {
|
||||
let mst: memio.state;
|
||||
let snk: io.stream;
|
||||
memio.dynamic(&mst, &snk);
|
||||
let st: memio.stream = memio.dynamic();
|
||||
let snk: io.stream = &st.vt;
|
||||
|
||||
let r = shlex.quote(&snk, in);
|
||||
let n: i32 = 0;
|
||||
let r = shlex.quote(snk, in);
|
||||
let n: size = 0;
|
||||
match (r) {
|
||||
case let v: i32 => { n = v; };
|
||||
case io.closed => { fail(); };
|
||||
case let v: size => { n = v; };
|
||||
case let _e: io.error => { fail(); };
|
||||
};
|
||||
if (n != expected.len) { fail(); };
|
||||
if (n != expected.len: size) { fail(); };
|
||||
|
||||
let view: str = memio.string(&mst);
|
||||
let view: str = memio.string(&st);
|
||||
if (!streq(view, expected)) { fail(); };
|
||||
|
||||
let _c = io.close(&snk);
|
||||
let _c: (void | io.error) = io.close(snk);
|
||||
};
|
||||
|
||||
// ---- split: Hare's @test fn split() table --------------------------
|
||||
|
||||
Reference in New Issue
Block a user