lib+test: fmt rename fprint→fdprint; add io.stream fprint sink
This commit is contained in:
5
Makefile
5
Makefile
@@ -230,6 +230,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||
$(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \
|
||||
$(BIN)/test_dyn_ww $(BIN)/test_selfcheck $(BIN)/test_at_test_ww \
|
||||
$(BIN)/test_fmt_run \
|
||||
$(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \
|
||||
$(BIN)/test_base32_run $(BIN)/test_base64_run \
|
||||
$(BIN)/test_adler32_run $(BIN)/test_crc16_run \
|
||||
@@ -409,6 +410,10 @@ $(BIN)/test_at_test_ww: test/wcc/997_at_test_ww.c $(BIN)/ww_ww $(BIN)/w6c_ww \
|
||||
$(BIN)/w6a_ww $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_fmt_run: test/wcc/970_fmt_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_memio_run: test/wcc/980_memio_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -29,7 +29,7 @@ Signatures mirror Hare too, modulo:
|
||||
- Call-site variadic sugar matches Hare. `fn f(args: T...)` declares
|
||||
a Hare-style variadic; call sites either gather N args into a
|
||||
fresh `[]T` (`fmt.println(42, "hi", true)`) or forward an existing
|
||||
slice with `xs...` (`fprintln(fd, args...)`). The bare `T...` form
|
||||
slice with `xs...` (`fdprintln(fd, args...)`). The bare `T...` form
|
||||
in tagged unions still means spread-flatten (`(...inner | E)`);
|
||||
the two uses don't overlap because `T...` only attaches to a
|
||||
*param* decl. `lib/fmt` is intentionally print-string-only — no
|
||||
|
||||
233
lib/fmt/fmt.ww
233
lib/fmt/fmt.ww
@@ -1,11 +1,84 @@
|
||||
// fmt — formatting writers. Mirrors Hare's lib/fmt subset that fmt-
|
||||
// prints values via [[io::handle]]-style fd writers. Call sites take
|
||||
// Hare's variadic shape: `fmt.println(42, "hi", true)` gathers the
|
||||
// args into a `[]formattable` slice; wrappers forward via `args...`.
|
||||
// fmt — formatting writers. Mirrors Hare's lib/fmt subset.
|
||||
//
|
||||
// Hare's `fmt::fprint` takes `io::handle = (io::file | int)`, which
|
||||
// ww doesn't yet have. So we ship two sinks side-by-side, with the
|
||||
// distinction baked into the name:
|
||||
//
|
||||
// fprint / fprintln write to a [[io.stream]] — Hare's
|
||||
// primary surface. Errors via `io.closed`.
|
||||
// fdprint / fdprintln write to a raw fd via [[os.write]] — ww-
|
||||
// specific. Errors via the raw `-errno` i64
|
||||
// convention. Used by the process-stdio
|
||||
// wrappers below (print/println/errorln/
|
||||
// fatal) until lib/io grows an fd-backed
|
||||
// stream; at that point both halves
|
||||
// graduate "in one go" (lib/CLAUDE.md) and
|
||||
// the fd-suffixed names disappear.
|
||||
//
|
||||
// Call sites take Hare's variadic shape: `fmt.println(42, "hi", true)`
|
||||
// gathers the args into a `[]formattable` slice; wrappers forward
|
||||
// via `args...`.
|
||||
|
||||
use os;
|
||||
use strconv;
|
||||
use strings;
|
||||
use io;
|
||||
|
||||
// Direct rt_syscall / rt_exit bindings rather than `use os;` because
|
||||
// os exports read/write/close, which collide with io.read/write/close
|
||||
// under the driver's flat-scope concat — same workaround used by
|
||||
// lib/memio. Both fmt's fd sink and io.stream sink need to coexist
|
||||
// in this module, so the os surface has to come in à la carte.
|
||||
@symbol("rt_syscall") fn rtsyscall3(num: i64, a: i64, b: i64, c: i64) i64;
|
||||
@symbol("rt_syscall") fn rtsyscall1(num: i64, a: i64) i64;
|
||||
|
||||
// rawwrite — Linux write(2) syscall (nr=1). The fd sinks below call
|
||||
// this directly instead of [[os.write]] to keep the collision off
|
||||
// fmt's exported surface. Same signature, same negative-errno
|
||||
// convention.
|
||||
fn rawwrite(fd: i32, buf: *u8, n: u64) i64 = {
|
||||
return rtsyscall3(1i64, fd: i64, buf: i64, n: i64);
|
||||
};
|
||||
|
||||
// rawexit — Linux exit(2) syscall (nr=60). Used only by `fatal`.
|
||||
fn rawexit(code: i32) void = {
|
||||
rtsyscall1(60i64, code: i64);
|
||||
};
|
||||
|
||||
// i64dec_buf — scratch buffer for [[i64dec]] below. Module-level
|
||||
// because Hare's `strconv::i64tos` is a static-buffer view and we
|
||||
// match that shape here. 21 bytes is enough for `-9223372036854775808`
|
||||
// (20 digits + sign).
|
||||
let i64dec_buf: [21]u8;
|
||||
|
||||
// i64dec — render `v` as a base-10 ASCII string into [[i64dec_buf]],
|
||||
// returning a borrowed view. Inlined here rather than calling
|
||||
// [[strconv.i64tos]] because `use strconv;` would transitively pull
|
||||
// `use os;`, whose exported read/write/close clash with
|
||||
// io.read/write/close under the driver's flat-scope concat. Same
|
||||
// algorithmic shape as strconv's version, narrowed to base-10.
|
||||
fn i64dec(v: i64) str = {
|
||||
let neg: bool = false;
|
||||
let n: i64 = v;
|
||||
if (n < 0) { neg = true; n = -n; };
|
||||
let tmp: [20]u8;
|
||||
let i: i32 = 0;
|
||||
if (n == 0) { tmp[0] = 48u8; i = 1; };
|
||||
for (n > 0) {
|
||||
let d: i64 = n % 10i64;
|
||||
tmp[i] = (d + 48i64): u8;
|
||||
n = n / 10i64;
|
||||
i += 1;
|
||||
};
|
||||
let out: i32 = 0;
|
||||
if (neg) { i64dec_buf[out] = 45u8; out += 1; }; // '-'
|
||||
for (i > 0) {
|
||||
i -= 1;
|
||||
i64dec_buf[out] = tmp[i];
|
||||
out += 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = &i64dec_buf[0];
|
||||
r.len = out;
|
||||
return r;
|
||||
};
|
||||
|
||||
// formattable — tagged union of types fmt can render. Mirrors Hare's
|
||||
// `fmt::formattable = (...types::numeric | uintptr | str | rune |
|
||||
@@ -13,41 +86,49 @@ use strings;
|
||||
// has codegen for. Slot size is 24B (8 tag + 16 str payload).
|
||||
export type formattable = (i64 | str | bool | rune);
|
||||
|
||||
// fprint — write the formatted form of each `args` element to `fd`,
|
||||
// ---- fd sinks --------------------------------------------------------
|
||||
|
||||
// fdprint — write the formatted form of each `args` element to `fd`,
|
||||
// separated by spaces. Returns total bytes written or the first
|
||||
// negative os.write result. Hare's separator-by-space matches.
|
||||
export fn fprint(fd: i32, args: formattable...) i64 = {
|
||||
// negative [[os.write]] result (Linux's `-errno`). Hare's separator-
|
||||
// by-space matches.
|
||||
//
|
||||
// Renamed from `fprint` once lib/fmt grew an io.stream sink (`fprint`
|
||||
// now points at that). This entry stays under `fd`-prefix until lib/io
|
||||
// can express the full Hare `io::handle = (file | int)` union, at
|
||||
// which point both halves graduate in one go.
|
||||
export fn fdprint(fd: i32, args: formattable...) i64 = {
|
||||
let total: i64 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < args.len) {
|
||||
if (i > 0) {
|
||||
let r: i64 = os.write(fd, " ".ptr, 1u64);
|
||||
let r: i64 = rawwrite(fd, " ".ptr, 1u64);
|
||||
if (r < 0) { return r; };
|
||||
total += r;
|
||||
};
|
||||
match (args[i]) {
|
||||
case let n: i64 => {
|
||||
let s: str = strconv.i64tos(n, strconv.base.DEC);
|
||||
let r: i64 = os.write(fd, s.ptr, s.len: u64);
|
||||
let s: str = i64dec(n);
|
||||
let r: i64 = rawwrite(fd, s.ptr, s.len: u64);
|
||||
if (r < 0) { return r; };
|
||||
total += r;
|
||||
};
|
||||
case let s: str => {
|
||||
let r: i64 = os.write(fd, s.ptr, s.len: u64);
|
||||
let r: i64 = rawwrite(fd, s.ptr, s.len: u64);
|
||||
if (r < 0) { return r; };
|
||||
total += r;
|
||||
};
|
||||
case let b: bool => {
|
||||
let s: str = "false";
|
||||
if (b) { s = "true"; };
|
||||
let r: i64 = os.write(fd, s.ptr, s.len: u64);
|
||||
let r: i64 = rawwrite(fd, s.ptr, s.len: u64);
|
||||
if (r < 0) { return r; };
|
||||
total += r;
|
||||
};
|
||||
case let r: rune => {
|
||||
let buf: [4]u8;
|
||||
buf[0] = r: u8;
|
||||
let n: i64 = os.write(fd, &buf[0], 1u64);
|
||||
let n: i64 = rawwrite(fd, &buf[0], 1u64);
|
||||
if (n < 0) { return n; };
|
||||
total += n;
|
||||
};
|
||||
@@ -57,40 +138,134 @@ export fn fprint(fd: i32, args: formattable...) i64 = {
|
||||
return total;
|
||||
};
|
||||
|
||||
// fprintln — fprint plus a trailing newline.
|
||||
export fn fprintln(fd: i32, args: formattable...) i64 = {
|
||||
let n: i64 = fprint(fd, args...);
|
||||
// fdprintln — fdprint plus a trailing newline.
|
||||
export fn fdprintln(fd: i32, args: formattable...) i64 = {
|
||||
let n: i64 = fdprint(fd, args...);
|
||||
if (n < 0) { return n; };
|
||||
let m: i64 = os.write(fd, "\n".ptr, 1u64);
|
||||
let m: i64 = rawwrite(fd, "\n".ptr, 1u64);
|
||||
if (m < 0) { return m; };
|
||||
return n + m;
|
||||
};
|
||||
|
||||
// print / println — fprint / fprintln on stdout. Direct counterparts
|
||||
// ---- stream sinks ----------------------------------------------------
|
||||
|
||||
// putbytes — internal helper that wraps (`*u8`, `i32`) into a `[]u8`
|
||||
// slice and feeds it to [[io.write]]. Not exported: callers compose
|
||||
// the same `(ptr, len)` triple as their underlying source (str view,
|
||||
// strconv buffer, stack rune buffer), and the slice is invariant
|
||||
// in shape across the formattable arms.
|
||||
fn putbytes(s: *io.stream, p: *u8, n: i32) (i32 | io.closed) = {
|
||||
let v: []u8;
|
||||
v.ptr = p;
|
||||
v.len = n;
|
||||
return io.write(s, v);
|
||||
};
|
||||
|
||||
// fprint — write the formatted form of each `args` element to `s`,
|
||||
// separated by spaces. Returns total bytes written, or `io.closed`
|
||||
// if the sink rejects mid-write. Mirrors Hare's `fmt::fprint` shape
|
||||
// for an `io::handle` sink, modulo ww's i32-sized byte counters and
|
||||
// the narrower `io.closed`-only error set on lib/io's stream vtable.
|
||||
//
|
||||
// A short write (sink accepts fewer bytes than asked) is reported by
|
||||
// the returned count, not as an error — matches [[io.write]]'s contract
|
||||
// per [[memio.fixedwrite]]. Callers that need write-all semantics layer
|
||||
// it on top, the same way they do over raw [[io.write]].
|
||||
export fn fprint(s: *io.stream, args: formattable...) (i32 | io.closed) = {
|
||||
let total: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < args.len) {
|
||||
if (i > 0) {
|
||||
let r: (i32 | io.closed) = putbytes(s, " ".ptr, 1);
|
||||
match (r) {
|
||||
case let n: i32 => { total += n; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
match (args[i]) {
|
||||
case let n: i64 => {
|
||||
let view: str = i64dec(n);
|
||||
let r: (i32 | io.closed) = putbytes(s, view.ptr, view.len);
|
||||
match (r) {
|
||||
case let m: i32 => { total += m; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
case let v: str => {
|
||||
let r: (i32 | io.closed) = putbytes(s, v.ptr, v.len);
|
||||
match (r) {
|
||||
case let m: i32 => { total += m; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
case let b: bool => {
|
||||
let v: str = "false";
|
||||
if (b) { v = "true"; };
|
||||
let r: (i32 | io.closed) = putbytes(s, v.ptr, v.len);
|
||||
match (r) {
|
||||
case let m: i32 => { total += m; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
case let r: rune => {
|
||||
let buf: [4]u8;
|
||||
buf[0] = r: u8;
|
||||
let rs: (i32 | io.closed) = io.write(s, buf[0:1]);
|
||||
match (rs) {
|
||||
case let m: i32 => { total += m; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
};
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// fprintln — fprint plus a trailing newline. Mirrors Hare's
|
||||
// `fmt::fprintln(io::handle, args...)`.
|
||||
export fn fprintln(s: *io.stream, args: formattable...) (i32 | io.closed) = {
|
||||
let total: i32 = 0;
|
||||
let r1: (i32 | io.closed) = fprint(s, args...);
|
||||
match (r1) {
|
||||
case let n: i32 => { total = n; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
let r2: (i32 | io.closed) = putbytes(s, "\n".ptr, 1);
|
||||
match (r2) {
|
||||
case let m: i32 => { total += m; };
|
||||
case io.closed => { let c: io.closed; return c; };
|
||||
};
|
||||
return total;
|
||||
};
|
||||
|
||||
// ---- process-stdio wrappers -----------------------------------------
|
||||
|
||||
// print / println — fdprint / fdprintln on stdout. Direct counterparts
|
||||
// of Hare's fmt::print / fmt::println.
|
||||
export fn print(args: formattable...) i64 = {
|
||||
return fprint(1, args...);
|
||||
return fdprint(1, args...);
|
||||
};
|
||||
|
||||
export fn println(args: formattable...) i64 = {
|
||||
return fprintln(1, args...);
|
||||
return fdprintln(1, args...);
|
||||
};
|
||||
|
||||
// errorln — fprintln on stderr. Hare's `fmt::error` (without -ln) is
|
||||
// errorln — fdprintln on stderr. Hare's `fmt::error` (without -ln) is
|
||||
// skipped here: the bare `error` name collides with strconv's
|
||||
// `type error = !(invalid | overflow)` under the driver's flat
|
||||
// concatenation namespace. Callers wanting the no-newline form use
|
||||
// `fprint(2, args...)` directly.
|
||||
// `fdprint(2, args...)` directly.
|
||||
export fn errorln(args: formattable...) i64 = {
|
||||
return fprintln(2, args...);
|
||||
return fdprintln(2, args...);
|
||||
};
|
||||
|
||||
// fatal — errorln then exit(255). `never` return marks the bottom
|
||||
// type so flow-control checks treat callers as terminated. The
|
||||
// fprintln result is dropped as an expression statement (Hare's
|
||||
// fdprintln result is dropped as an expression statement (Hare's
|
||||
// `_ = fprintln(...)` wouldn't add safety here — process exit
|
||||
// follows immediately).
|
||||
export fn fatal(args: formattable...) never = {
|
||||
fprintln(2, args...);
|
||||
os.exit(255);
|
||||
fdprintln(2, args...);
|
||||
rawexit(255);
|
||||
};
|
||||
|
||||
213
lib/fmt/fmttest.ww
Normal file
213
lib/fmt/fmttest.ww
Normal file
@@ -0,0 +1,213 @@
|
||||
// fmttest — exercises lib/fmt's stream sinks (fprint / fprintln).
|
||||
// Run with `out/bin/ww run lib/fmt/fmttest.ww`.
|
||||
//
|
||||
// Each @test writes through a memio.stream and compares the resulting
|
||||
// bytes against an inline `want` literal. Bodies are short enough that
|
||||
// the parallel-array idiom used by memiotest doesn't apply — every row
|
||||
// here threads a different variadic argument-pack into fprint, and the
|
||||
// variadic shape can't be table-driven within a single fn body.
|
||||
|
||||
use fmt;
|
||||
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.
|
||||
@symbol("rt_syscall") fn syscall1ww(num: i64, a: i64) i64;
|
||||
fn doexit(code: i32) void = {
|
||||
syscall1ww(60i64, code: i64);
|
||||
};
|
||||
|
||||
// signalled — bumped before each scenario so a failing exit code
|
||||
// pinpoints the offending case.
|
||||
let signalled: i32 = 0;
|
||||
|
||||
fn fail() void = { doexit(signalled + 10); };
|
||||
|
||||
fn streq(a: str, b: str) bool = {
|
||||
if (a.len != b.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) {
|
||||
if (a[i] != b[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// ---- A write-always-closed stream for the io.closed surfacing test ----
|
||||
|
||||
fn closedread(s: *io.stream, buf: []u8) (i32 | io.eof | io.closed) = {
|
||||
let e: io.closed; return e;
|
||||
};
|
||||
fn closedwrite(s: *io.stream, buf: []u8) (i32 | io.closed) = {
|
||||
let e: io.closed; return e;
|
||||
};
|
||||
fn closedclose(s: *io.stream) (void | io.closed) = { return; };
|
||||
|
||||
fn closedstream(s: *io.stream) void = {
|
||||
s.ctx = nil;
|
||||
s.read = closedread;
|
||||
s.write = closedwrite;
|
||||
s.close = closedclose;
|
||||
};
|
||||
|
||||
// ---- fprint: bare str --------------------------------------------------
|
||||
|
||||
@test fn fprintbarestr() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, "hello");
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 5) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "hello")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint: int + str mix, space-separated ----------------------------
|
||||
|
||||
@test fn fprintintstr() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, 42i64, "x");
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 4) { fail(); }; }; // "42 x"
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "42 x")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint: bool + rune renders as "true A" --------------------------
|
||||
|
||||
@test fn fprintboolrune() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, true, 'A': rune);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 6) { fail(); }; }; // "true A"
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "true A")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint: zero args returns 0, no bytes written --------------------
|
||||
|
||||
@test fn fprintempty() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 0) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (memio.string(&mem).len != 0) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprintln: multi-arg, trailing '\n' --------------------------------
|
||||
|
||||
@test fn fprintlnmulti() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprintln(&s, "a", 1i64, false);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 10) { fail(); }; }; // "a 1 false\n"
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "a 1 false\n")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprintln: zero args writes just the newline ----------------------
|
||||
|
||||
@test fn fprintlnempty() void = {
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.dynamic(&mem, &s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprintln(&s);
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 1) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "\n")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint over a fixed stream: short writes return partial count ----
|
||||
// memio.fixedwrite caps each call at the remaining buffer space and
|
||||
// never closes the stream, so fprint sees a short i32 result, not
|
||||
// io.closed. Verifies the inner-loop arithmetic adds the actual byte
|
||||
// count rather than the requested length.
|
||||
|
||||
@test fn fprintfixedshort() void = {
|
||||
let buf: [3]u8;
|
||||
let mem: memio.state;
|
||||
let s: io.stream;
|
||||
memio.fixed(&mem, &s, buf[0:3]);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, "hello");
|
||||
match (r) {
|
||||
case let n: i32 => { if (n != 3) { fail(); }; };
|
||||
case io.closed => fail();
|
||||
};
|
||||
if (!streq(memio.string(&mem), "hel")) { fail(); };
|
||||
|
||||
let c: (void | io.closed) = io.close(&s);
|
||||
match (c) { case void => {}; case io.closed => fail(); };
|
||||
};
|
||||
|
||||
// ---- fprint over a closed stream surfaces io.closed -------------------
|
||||
// Exercises the early-return arm in fprint's inner match — distinct from
|
||||
// the short-write path above, which keeps returning i32 from a partial
|
||||
// accept. Single arm is enough: every formattable case routes errors
|
||||
// through the same putbytes / io.write wiring.
|
||||
|
||||
@test fn fprintclosed() void = {
|
||||
let s: io.stream;
|
||||
closedstream(&s);
|
||||
|
||||
let r: (i32 | io.closed) = fmt.fprint(&s, "x");
|
||||
match (r) {
|
||||
case let n: i32 => fail();
|
||||
case io.closed => {};
|
||||
};
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; fprintbarestr();
|
||||
signalled = 2; fprintintstr();
|
||||
signalled = 3; fprintboolrune();
|
||||
signalled = 4; fprintempty();
|
||||
signalled = 5; fprintlnmulti();
|
||||
signalled = 6; fprintlnempty();
|
||||
signalled = 7; fprintfixedshort();
|
||||
signalled = 8; fprintclosed();
|
||||
return 0;
|
||||
};
|
||||
@@ -1639,7 +1639,7 @@ static const struct row rows[] = {
|
||||
"fn main() i32 = { return sumtag(1i64, \"hi\", true): i32; };", 42 },
|
||||
/* Variadic forwarding: `wrap(args...)` passes the local slice
|
||||
* directly to `sum`, no re-gather. Mirrors Hare's wrapper shape
|
||||
* (`fn println(args: formattable...) = fprintln(os.stdout, args...)`). */
|
||||
* (`fn println(args: formattable...) = fdprintln(os.stdout, args...)`). */
|
||||
{ "fn sum(args: i64...) i64 = {\n"
|
||||
" let s: i64 = 0i64;\n"
|
||||
" let i: i32 = 0;\n"
|
||||
@@ -1654,8 +1654,8 @@ static const struct row rows[] = {
|
||||
"};", 42 },
|
||||
/* lib/fmt user-side: `fmt.println(args: formattable...)` gathers
|
||||
* mixed-type args at the call site. End-to-end exercises the
|
||||
* lib/fmt graduation: the wrapper-chain `println → fprintln →
|
||||
* fprint` is itself variadic-forwarding, so this validates both
|
||||
* lib/fmt graduation: the wrapper-chain `println → fdprintln →
|
||||
* fdprint` is itself variadic-forwarding, so this validates both
|
||||
* gather (at main) and `args...` forward (inside lib/fmt). The
|
||||
* exit code is bytes printed (`hello 7\n` = 8). */
|
||||
{ "use fmt;\n"
|
||||
|
||||
@@ -33,13 +33,13 @@ static const char *modules[] = {
|
||||
"lib/math/random/random.ww",
|
||||
"lib/time/time.ww",
|
||||
"lib/c/libc/libc.ww",
|
||||
/* lib/bufio/bufio.ww moved off this list: graduates to the
|
||||
* io.stream-based scanner, which carries cross-module type
|
||||
* refs (*io.stream, io.eof, io.closed) that only resolve once
|
||||
* the driver concatenates `use`d modules. Coverage lives at
|
||||
* lib/bufio/bufiotest.ww + the bufio.scanline e2e row in
|
||||
* test/wcc/700_e2e.c. */
|
||||
"lib/fmt/fmt.ww",
|
||||
/* lib/bufio/bufio.ww and lib/fmt/fmt.ww moved off this list:
|
||||
* both graduated to io.stream-based sinks, which carry cross-
|
||||
* module type refs (*io.stream, io.closed, io.eof) that only
|
||||
* resolve once the driver concatenates `use`d modules. Coverage
|
||||
* lives at lib/bufio/bufiotest.ww + lib/fmt/fmttest.ww (wired
|
||||
* at 998_bufio_run.c and 970_fmt_run.c), plus the bufio.scanline
|
||||
* and fmt.println e2e rows in test/wcc/700_e2e.c. */
|
||||
"lib/net/net.ww",
|
||||
NULL
|
||||
};
|
||||
|
||||
52
test/wcc/970_fmt_run.c
Normal file
52
test/wcc/970_fmt_run.c
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 970_fmt_run — execute the lib/fmt @test fixture under the C-side
|
||||
* `ww run` driver and assert exit 0.
|
||||
*
|
||||
* fmt sits below io conceptually (sinks formatted bytes into an
|
||||
* io.stream); slotting at 970 keeps it ahead of the 980-989
|
||||
* stdlib-run sub-band that depends on it. fmttest.ww carries its
|
||||
* own `export fn main()` that drives the @test fns and signals
|
||||
* which case failed via the exit code, so this file is a thin
|
||||
* wrapper — no @test scanning, no synthetic main generation.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "lib/fmt/fmttest.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "fmt_run FAIL: %s exited %d\n", src, rc);
|
||||
return 1;
|
||||
}
|
||||
printf("fmt_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user