Files
ww/lib/bufio/bufio.ww
Hojun-Cho dd188ca460 ww: add Hare-style is/as postfix ops on tagged unions
`e is T` returns bool (variant tag == T's index); `e as T` unwraps
to T or exit(1) on mismatch. Postfix, same precedence as `:` cast.
TK_IS / N_TYPETEST / N_TYPEASSERT appended at the tail of their
enums so every prior numeric value stays unchanged — the
990_selfhost wwdump-diff stays byte-clean.

Cgen mirrors the match-case slot-based load (tag at +0, value at
+8/+16), so an N_IDENT tagged-union local works just like a
match scrutinee. Selfhost cgen inlines the slot resolution
because the wwstage cgen drops sign bits on `*i32` output
parameters in this position.

Renames `errors.is` -> `errors.equal` (the only naming collision;
the existing comment already noted it shared shape with
strings.equal/bytes.equal).
2026-05-11 23:21:08 +09:00

83 lines
2.2 KiB
Plaintext

// bufio — buffered reader/writer over io.stream. Plan 9 'bio'
// analogue, lowered to ww. The buffer is owned by the caller and
// passed in at init time; we don't allocate.
//
// We keep the stream pointer as *void here to avoid a cross-module
// type name (the module-import system isn't online yet); a later
// revision will replace it with *io.stream once `use` resolves
// types from imported modules.
type streamp = *void;
type buf = struct {
s: streamp,
data: *u8,
cap: i32,
r: i32, // read cursor
w: i32, // write cursor (for writers)
};
export fn rinit(b: *buf, s: streamp, data: *u8, cap: i32) void = {
b.s = s;
b.data = data;
b.cap = cap;
b.r = 0;
b.w = 0;
};
// peek1 — look at the next byte without consuming. Returns -1 on
// empty buffer; the caller is responsible for refilling via the
// stream when this happens.
export fn peek1(b: *buf) i32 = {
if (b.r < b.w) {
return b.data[b.r]: i32;
};
return -1;
};
// take1 — pop one byte. -1 if empty.
export fn take1(b: *buf) i32 = {
if (b.r < b.w) {
let c: u8 = b.data[b.r];
b.r += 1;
return c: i32;
};
return -1;
};
// avail — bytes left to read out of the buffer.
export fn avail(b: *buf) i32 = {
return b.w - b.r;
};
// Distinct alias so `(str | linerr)` has two variant types the
// tagged-union machinery can keep apart at the tag level. The error
// variant carries a short description; callers compare with errors.equal
// or just inspect by length.
type linerr = str;
// takeline — Hare-style fallible line read. Drains the buffer up to
// (but not including) the next '\n' and advances the cursor past the
// newline. Returns the line as a borrowed str on success, or a linerr
// describing why no line was available:
// - "eof" when the buffer is empty
// - "no newline" when the buffer contains data but no '\n'
//
// The returned str borrows from the underlying buffer; callers must
// consume it (or copy) before refilling.
export fn takeline(b: *buf) (str | linerr) = {
if (b.r >= b.w) { return "eof": linerr; };
let i: i32 = b.r;
for (i < b.w) {
if (b.data[i] == 10u8) {
let s: str;
s.ptr = b.data + b.r;
s.len = i - b.r;
b.r = i + 1;
return s;
};
i += 1;
};
return "no newline": linerr;
};