lib: banner purge + WHY-only comment sweep (rule 8)

Every // ---- section banner dies (132 -> 0): names carry the WHAT.
Narration deleted (filename restatements, run-with lines, what-the-
next-line-does); every ref/hare cite, task cite, divergence, ABI/
layout contract, and ownership qualifier kept (borrowed-view lines
restored where the sweep over-cut). Comment-only proven: all 442
walk-workdir .s and 32 import-probe .s byte-identical before/after;
libbyteid 56-roster all-ID.
This commit is contained in:
2026-08-08 21:10:18 +09:00
parent 659e859f34
commit aadc6618f0
90 changed files with 259 additions and 919 deletions

View File

@@ -1,6 +1,5 @@
// asciitest — exercises lib/ascii case folding. Run with
// `ww run lib/ascii/asciitest.ww`. A failing row aborts via the
// assert/abort builtin (task #5 @test conversion).
// A failing row aborts via the assert/abort builtin (task #5 @test
// conversion).
//
// Vectors mirror Hare's @test fn strcasecmp in ref/hare/ascii/string.ha.

View File

@@ -1,9 +1,6 @@
// scannertest — exercises the lib/bufio scanner surface. The @tests
// enumerate parallel `[N]T` arrays of inputs and expectations, then
// iterate one body across them. #94 fold-eFinal: the unified
// value-return surface — `let st = memio.fixed(buf); &st.vt` into the
// scanner init, io.read/write/close return size/io.error. Row
// ownership mirrors ref/hare/bufio/scanner_test+test.ha.
// Row ownership mirrors ref/hare/bufio/scanner_test+test.ha.
// Streams are built as `let st = memio.fixed(buf); &st.vt` — the #94
// unified value-return io surface (read/write/close return size/io.error).
package bufio_test;
@@ -12,8 +9,6 @@ import encoding.utf8;
import io;
import memio;
// putstr — copy the bytes of `s` into `into` starting at `off`,
// returning the new offset.
fn putstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < s.len) {
@@ -23,8 +18,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
return off + s.len;
};
// Replaces the OLD io.closed source. errvt is module-static; its
// reader/writer return a nomem-widened io.error.
let errvt: io.vtable;
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
@@ -244,7 +237,6 @@ fn errsource() io.stream = {
};
@test fn boundarycases() void = {
// ---- empty line via scanline -----------------------------------
let s1: [16]u8;
let n1: i32 = putstr("\nfoo\n", s1[0:16], 0);
let m1mem: memio.stream = memio.fixed(s1[0:n1]);
@@ -271,7 +263,6 @@ fn errsource() io.stream = {
};
bufio.finish(&sc1);
// ---- scanbytes idempotent EOF ---------------------------------
let s2: [8]u8;
let n2: i32 = putstr("ab", s2[0:8], 0);
let m2mem: memio.stream = memio.fixed(s2[0:n2]);

View File

@@ -2,24 +2,6 @@
// Hare's bufio:: surface (ref/hare/bufio/{scanner,stream}.ha). Project
// #94 fold-eFinal.
//
// Surface:
//
// bufio.newscanner (src: io.stream, maxread: i32) scanner
// bufio.newscannerbuf(src: io.stream, buf: []u8) scanner
// bufio.finish (s: *scanner) void
// bufio.scanbyte (s: *scanner) (u8 | io.eof | io.error | overflow)
// bufio.scanbytes (s: *scanner, delim: u8)
// ([]u8 | io.eof | io.error | overflow)
// bufio.scanrune (s: *scanner)
// (rune | io.eof | io.error | utf8.invalid | overflow)
// bufio.scanline (s: *scanner) (str | io.eof | io.error | overflow)
//
// bufio.init (src: io.stream, rbuf: []u8, wbuf: []u8) stream
// bufio.flush (b: *stream) (void | io.error)
// bufio.setflush (b: *stream, bs: []u8) void
// bufio.unread (b: *stream, buf: []u8) void
// bufio.isbuffered (s: io.stream) bool
//
// VALUE-RETURN (Hare ref/hare/bufio/stream.ha:69 init, scanner.ha:92
// newscanner_buf): each constructor builds in a local `let r: T;`,
// field-assigns every slot, and `return r;`. The caller owns the

View File

@@ -1,8 +1,6 @@
// streamtest — exercises the lib/bufio buffered stream surface. The
// stream @tests are one scenario per fn. #94 fold-eFinal: the unified
// value-return surface — `let st = memio.fixed(buf); &st.vt` into the
// stream init, io.read/write/close return size/io.error. Row
// ownership mirrors ref/hare/bufio/stream_test+test.ha.
// Row ownership mirrors ref/hare/bufio/stream_test+test.ha.
// Streams are built as `let st = memio.fixed(buf); &st.vt` — the #94
// unified value-return io surface (read/write/close return size/io.error).
package bufio_test;
@@ -10,8 +8,6 @@ import bufio;
import io;
import memio;
// sputstr — copy the bytes of `s` into `into` starting at `off`,
// returning the new offset.
fn sputstr(s: str, into: []u8, off: i32) i32 = {
let i: i32 = 0;
for (i < s.len) {

View File

@@ -1,6 +1,4 @@
// bytes — slice operations over []u8. Mirrors Hare's bytes module
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
// helpers used by lib/encoding, lib/bufio, lib/memio.
// Hare port of the in-tree subset; see ref/hare/bytes/.
//
// Documented divergences from Hare:
// - index_slice / rindex_slice use naive O(n·m); Hare specialises
@@ -29,17 +27,16 @@ import types;
// (utf8.ww:36). Plain `void` (not `!void`): continuation signal.
export type done = void;
// tokenizer — cursor over an input slice. Layout mirrors
// ref/hare/bytes/tokenize.ha:6-10. `p` is the cached peek-position;
// I64_MAX (forward) / I64_MIN (reverse) are the unprimed sentinels.
// p < 0 also identifies a reverse-direction iterator.
// Layout mirrors ref/hare/bytes/tokenize.ha:6-10. `p` is the cached
// peek-position; I64_MAX (forward) / I64_MIN (reverse) are the
// unprimed sentinels. p < 0 also identifies a reverse-direction
// iterator.
export type tokenizer = struct {
in: []u8,
delim: []u8,
p: i64,
};
// equal — true iff `a` and `b` have the same length and contents.
// ref/hare/bytes/equal.ha:9.
export fn equal(a: []u8, b: []u8) bool = {
if (a.len != b.len) { return false; };
@@ -51,8 +48,6 @@ export fn equal(a: []u8, b: []u8) bool = {
return true;
};
// index — first offset of `needle` in `s`. u8 needle scans for the
// byte; []u8 needle scans for the substring. void if absent.
// ref/hare/bytes/index.ha:6.
export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
match (needle) {
@@ -85,9 +80,8 @@ export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
return;
};
// rindex — last offset of `needle` in `s`. Empty []u8 needle returns
// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0).
// ref/hare/bytes/index.ha:86.
// Empty []u8 needle returns s.len (ref/hare/bytes/index.ha:103 —
// Hare's loop yields r-0 at i=0). ref/hare/bytes/index.ha:86.
export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
match (needle) {
case let c: u8 => {
@@ -118,7 +112,6 @@ export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
return;
};
// contains — true iff any of `needles` (byte or sub-slice) appears in `s`.
// ref/hare/bytes/contains.ha:6.
export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
let i: i32 = 0;
@@ -142,8 +135,7 @@ export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
return false;
};
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
// ref/hare/bytes/trim.ha:7. Borrowed view of `in` — caller must not free.
export fn ltrim(in: []u8, trim: u8...) []u8 = {
assert(trim.len > 0, "bytes.ltrim called with empty trim set");
let i: i32 = 0;
@@ -155,8 +147,7 @@ export fn ltrim(in: []u8, trim: u8...) []u8 = {
return r;
};
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
// ref/hare/bytes/trim.ha:17. Borrowed view of `in`. Hare's loop uses
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
// the equivalent termination is spelled `i >= 0` explicitly.
export fn rtrim(in: []u8, trim: u8...) []u8 = {
@@ -170,13 +161,11 @@ export fn rtrim(in: []u8, trim: u8...) []u8 = {
return r;
};
// trim — borrowed view of `in` with both ends in `trim` stripped.
// ref/hare/bytes/trim.ha:27.
// ref/hare/bytes/trim.ha:27. Borrowed view of `in`.
export fn trim(in: []u8, trim: u8...) []u8 = {
return ltrim(rtrim(in, trim...), trim...);
};
// hasprefix — true iff `s` starts with `pre`.
// ref/hare/bytes/contains.ha:21.
export fn hasprefix(s: []u8, pre: []u8) bool = {
if (pre.len > s.len) { return false; };
@@ -188,7 +177,6 @@ export fn hasprefix(s: []u8, pre: []u8) bool = {
return true;
};
// hassuffix — true iff `s` ends with `suf`.
// ref/hare/bytes/contains.ha:35.
export fn hassuffix(s: []u8, suf: []u8) bool = {
if (suf.len > s.len) { return false; };
@@ -201,7 +189,7 @@ export fn hassuffix(s: []u8, suf: []u8) bool = {
return true;
};
// reverse — in-place reverse of `s`. ref/hare/bytes/reverse.ha:5.
// ref/hare/bytes/reverse.ha:5.
export fn reverse(s: []u8) void = {
let i: i32 = 0;
let j: i32 = s.len - 1;
@@ -214,7 +202,7 @@ export fn reverse(s: []u8) void = {
};
};
// zero — set every byte of `s` to 0. ref/hare/bytes/zero.ha:5.
// ref/hare/bytes/zero.ha:5.
export fn zero(s: []u8) void = {
let i: i32 = 0;
for (i < s.len) {
@@ -223,8 +211,6 @@ export fn zero(s: []u8) void = {
};
};
// tokenize — iterator yielding tokens from `in` separated by any byte
// in `delim`. Leading / trailing / adjacent delims yield empty tokens.
// `delim` is borrowed; caller keeps it valid for the tokenizer's
// lifetime. ref/hare/bytes/tokenize.ha:22.
export fn tokenize(in: []u8, delim: u8...) tokenizer = {
@@ -242,8 +228,7 @@ export fn tokenize(in: []u8, delim: u8...) tokenizer = {
return t;
};
// rtokenize — reverse-direction tokenize. First nexttoken yields the
// last token, last nexttoken yields the first. ref/hare/bytes/tokenize.ha:40.
// ref/hare/bytes/tokenize.ha:40.
export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
assert(delim.len > 0, "bytes.rtokenize called with empty slice");
assert((in.len: i64) < types.I64_MAX,
@@ -259,9 +244,8 @@ export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
return t;
};
// peektoken — next token without advancing the cursor. Returns done
// once `s.delim` has been zeroed by a prior past-end nexttoken.
// ref/hare/bytes/tokenize.ha:91.
// Returns done once `s.delim` has been zeroed by a prior past-end
// nexttoken. ref/hare/bytes/tokenize.ha:91.
export fn peektoken(s: *tokenizer) ([]u8 | done) = {
if (s.delim.len == 0) {
let d: done; return d;
@@ -338,7 +322,6 @@ export fn peektoken(s: *tokenizer) ([]u8 | done) = {
return r;
};
// nexttoken — current token, then advance past it and the delim.
// Once the input is exhausted, returns done and zeros `s.delim` so
// subsequent peeks short-circuit. ref/hare/bytes/tokenize.ha:59.
export fn nexttoken(s: *tokenizer) ([]u8 | done) = {
@@ -380,15 +363,13 @@ export fn nexttoken(s: *tokenizer) ([]u8 | done) = {
return b;
};
// remainingtokens — the unconsumed portion of `s.in`. Read-only view.
// ref/hare/bytes/tokenize.ha:145.
// ref/hare/bytes/tokenize.ha:145. Read-only borrowed view.
export fn remainingtokens(s: *tokenizer) []u8 = {
return s.in;
};
// splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder.
// The trailing slot (when more than `n - 1` tokens exist) holds the
// unconsumed remainder.
//
// The caller frees the returned slice via
// `os.free(r.ptr: *void, (r.cap: u64) * 24u64)`. Element bytes are
@@ -425,9 +406,8 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
return toks;
};
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
// collected from the end of `in`. The trailing slot holds the
// unconsumed prefix (everything before the n-th-from-last delim hit).
// The trailing slot holds the unconsumed prefix (everything before
// the n-th-from-last delim hit).
//
// When the input has fewer than n tokens, the `done` short-circuit
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
@@ -486,8 +466,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
return toks;
};
// split — full split of `in` on `delim` (no token cap). Mirrors
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
// Mirrors `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
// because the index type is i32 (lib/CLAUDE.md).
//
// ref/hare/bytes/tokenize.ha:225.
@@ -495,10 +474,9 @@ export fn split(in: []u8, delim: []u8) [][]u8 = {
return splitn(in, delim, types.I32_MAX);
};
// cut — split `in` along the first instance of `delim`, returning the
// portion before and the portion after the delimiter as a borrowed
// tuple. When `delim` is absent, the whole input is the first half and
// the second is empty. ref/hare/bytes/tokenize.ha:392.
// When `delim` is absent, the whole input is the first half and the
// second is empty. Both halves are borrowed views — caller must not
// free them. ref/hare/bytes/tokenize.ha:392.
//
// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91);
// the tagged union is an unordered set, so this is the same type as
@@ -525,7 +503,6 @@ export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
};
};
// rcut — like [[cut]] but splits along the last instance of `delim`.
// ref/hare/bytes/tokenize.ha:413.
export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
let ln: i32 = match (delim) {

View File

@@ -1,6 +1,4 @@
// containstest — exercises bytes.contains/hasprefix/hassuffix. A
// failing row aborts via the assert/abort builtin (task #5 @test
// conversion). Vectors mirror ref/hare/bytes/contains.ha.
// Vectors mirror ref/hare/bytes/contains.ha (task #5 @test conversion).
package bytes_test;

View File

@@ -1,6 +1,4 @@
// equaltest — exercises bytes.equal. A failing row aborts via the
// assert/abort builtin (task #5 @test conversion).
// Vectors mirror ref/hare/bytes/equal.ha.
// Vectors mirror ref/hare/bytes/equal.ha (task #5 @test conversion).
package bytes_test;

View File

@@ -1,6 +1,4 @@
// indextest — exercises bytes.index/rindex, u8 and []u8 arms. A
// failing row aborts via the assert/abort builtin (task #5 @test
// conversion). Vectors mirror ref/hare/bytes/index.ha.
// Vectors mirror ref/hare/bytes/index.ha (task #5 @test conversion).
package bytes_test;

View File

@@ -1,6 +1,4 @@
// tokenizetest — exercises the bytes tokenize/splitn/cut families.
// A failing row aborts via the assert/abort builtin (task #5 @test
// conversion). Vectors mirror ref/hare/bytes/tokenize.ha.
// Vectors mirror ref/hare/bytes/tokenize.ha (task #5 @test conversion).
package bytes_test;
@@ -11,8 +9,6 @@ import os;
// drives the iterator through an expected-token sequence and asserts
// `equal(p, n)` (peek == next), `equal(n, want)` (next == expected).
// expect_token — table row driver. Advances `t` once, asserts the
// returned token matches `want`. peek invariant: peek must equal next.
fn expect_token(t: *bytes.tokenizer, want: []u8) void = {
match (bytes.peektoken(t)) {
case let p: []u8 => {
@@ -28,7 +24,6 @@ fn expect_token(t: *bytes.tokenizer, want: []u8) void = {
};
};
// expect_done — table-row driver. peek and next must both be done.
fn expect_done(t: *bytes.tokenizer) void = {
match (bytes.peektoken(t)) {
case let p: []u8 => abort();

View File

@@ -1,6 +1,4 @@
// trimtest — exercises bytes.ltrim/rtrim/trim. A failing row aborts
// via the assert/abort builtin (task #5 @test conversion).
// Vectors mirror ref/hare/bytes/trim.ha.
// Vectors mirror ref/hare/bytes/trim.ha (task #5 @test conversion).
package bytes_test;

View File

@@ -1,10 +1,8 @@
// lib/c/libc — minimal libc bindings. Each declaration is body-less,
// imported from the C side at link time. The @symbol attribute pins
// the linker name; without it, the binding name itself is used.
//
// These declarations cover the small surface the bootstrap wants:
// process exit, three io syscalls, and the malloc/free pair. Higher
// ergonomics live in sibling pure-ww packages.
// ergonomics live in sibling pure-ww packages. Each declaration is
// body-less, resolved from the C side at link time; @symbol pins the
// linker name (without it the binding name itself is used).
package libc;

View File

@@ -1,13 +1,11 @@
// sha256_test — exercises lib/crypto/sha256 against the standard NIST
// SHA-256 vectors (FIPS 180-4 examples + the classic "one million a's").
// Run with `out/bin/ww run lib/crypto/sha256/sha256_test.ww`.
// Vectors: the standard NIST SHA-256 set (FIPS 180-4 examples + the
// classic "one million a's").
//
// The digest is the cgen-correctness oracle for u32 wrapping arithmetic
// + the hash-vtable dispatch: any u32-overflow / rotate miscompile shows
// up as a byte mismatch. A failing row aborts via the assert/abort
// builtin (task #5 @test conversion). Expected digests come through the
// (separately tested)
// hex.decodestr so the vectors stay readable.
// (separately tested) hex.decodestr so the vectors stay readable.
package sha256_test;
import bytes;

View File

@@ -1,14 +1,7 @@
// dirs — XDG base directory paths. Port of Hare's lib/dirs
// (ref/hare/dirs/xdg.ha) using the lib/temp-style static `[256]u8`
// pathbuf in place of Hare's `path::buffer` (lib/path doesn't ship
// a buffer type yet).
//
// Surface today (1:1 with Hare's xdg.ha minus runtime()):
//
// dirs.config(prog: str) str — XDG_CONFIG_HOME/<prog>, fallback $HOME/.config/<prog>
// dirs.cache(prog: str) str — XDG_CACHE_HOME/<prog>, fallback $HOME/.cache/<prog>
// dirs.data(prog: str) str — XDG_DATA_HOME/<prog>, fallback $HOME/.local/share/<prog>
// dirs.state(prog: str) str — XDG_STATE_HOME/<prog>, fallback $HOME/.local/state/<prog>
// a buffer type yet). Surface is 1:1 with Hare's xdg.ha minus runtime().
//
// Returns are static-buffer views — borrowed for the lifetime of
// the next dirs call (any of the four above). Callers needing the
@@ -71,9 +64,8 @@ def SEP: u8 = 47u8;
// octal literals, so we name the constant once.
def MODE_0755: i32 = 493i32;
// puts — append `s` to pathbuf at offset `off`, capping against the
// buffer's capacity to leave room for the trailing NUL. Returns the
// new offset. Same shape as lib/temp.puts.
// Caps at 255 to leave room for the trailing NUL. Same shape as
// lib/temp.puts.
fn puts(off: i32, s: str) i32 = {
let i: i32 = 0;
for (i < s.len) {
@@ -84,9 +76,6 @@ fn puts(off: i32, s: str) i32 = {
return off + i;
};
// build — assemble "<base>/<sub>/<prog>" into pathbuf, NUL-terminate,
// and store the length in [[pathlen]]. If `sub` is empty, the
// "/<sub>" segment is skipped and the result is "<base>/<prog>".
// Embedded '/' in `sub` (e.g. ".local/share") is fine — [[os.mkdirs]]
// handles intermediate dirs.
fn build(base: str, sub: str, prog: str) void = {
@@ -112,8 +101,7 @@ fn build(base: str, sub: str, prog: str) void = {
pathlen = off;
};
// view — return a `str` view into pathbuf[0..pathlen]. Borrowed
// for the lifetime of the next dirs call.
// Borrowed for the lifetime of the next dirs call.
fn view() str = {
let r: str;
r.ptr = &pathbuf[0];

View File

@@ -92,8 +92,6 @@ fn gettmpdir() str = {
};
};
// ---- 1: XDG_CONFIG_HOME=<tmpl>/cfg → <tmpl>/cfg/myapp --------------
@test fn test_config_xdg_set() void = {
let tmpl = gettmpdir();
let exp = expected(tmpl, "cfg", "myapp");
@@ -101,8 +99,6 @@ fn gettmpdir() str = {
assert(!(!streq(got, exp)));
};
// ---- 2: XDG_CACHE_HOME="relative/path" → fallback to $HOME/.cache --
//
// Exercises Hare's `if (!path::abs(path)) yield;` branch — a
// non-absolute XDG_*_HOME silently falls through to the HOME path.
@@ -113,8 +109,6 @@ fn gettmpdir() str = {
assert(!(!streq(got, exp)));
};
// ---- 3: XDG_DATA_HOME unset → fallback to $HOME/.local/share -------
@test fn test_data_unset_fallback() void = {
let tmpl = gettmpdir();
let exp = expected(tmpl, ".local/share", "myapp");
@@ -122,8 +116,6 @@ fn gettmpdir() str = {
assert(!(!streq(got, exp)));
};
// ---- 4: XDG_STATE_HOME unset → fallback to $HOME/.local/state ------
@test fn test_state_unset_fallback() void = {
let tmpl = gettmpdir();
let exp = expected(tmpl, ".local/state", "myapp");

View File

@@ -1,5 +1,3 @@
// encoding/base32 — RFC 4648 base32 encode/decode, buffer-based.
//
// Mirrors Hare's encoding::base32 surface, modulo Hare's stream-based
// encoder/decoder. ww ships the in-memory subset only: `encode(dst,
// src)` writes the encoded bytes into `dst`, returning the count;

View File

@@ -86,7 +86,6 @@ fn enchexvec(input: str, expect: str) void = {
};
@test fn roundtrip_all_quintets() void = {
// Encode then decode every 5-byte combination of a small set.
let raw: [5]u8;
raw[0] = 0x00u8;
raw[1] = 0x55u8;

View File

@@ -1,9 +1,7 @@
// base64_test — exercises lib/encoding/base64's io-streaming surface.
// Run with `out/bin/ww run lib/encoding/base64/base64_test.ww`. Mirrors
// Hare's base64 @test fns (ref/hare/encoding/base64/base64.ha:315,514,
// 601) over the RFC 4648 §10 vectors, table-driven (parallel arrays;
// tuple-row arrays are blocked by #111). A failing row aborts via the
// assert/abort builtin (task #5 @test conversion).
// Mirrors Hare's base64 @test fns (ref/hare/encoding/base64/base64.ha:
// 315,514,601) over the RFC 4648 §10 vectors, table-driven (parallel
// arrays; tuple-row arrays are blocked by #111). A failing row aborts
// via the assert/abort builtin (task #5 @test conversion).
//
// The streaming decoder (newdecoder) is deferred (#247-sibling), so the
// decode side is exercised through decodestr only.
@@ -60,8 +58,6 @@ fn inval_check(enc: *base64.encoding, encoded: str) void = {
};
};
// ---- RFC 4648 §10 vectors, encode + decodestr round-trip ----
//
// Inputs are the prefixes of "foobar". The §10 expected encodings
// contain no '+' / '/', so std and base64url agree on these vectors —
// both alphabets are driven over the same table here; the std-vs-url
@@ -91,8 +87,6 @@ fn inval_check(enc: *base64.encoding, encoded: str) void = {
};
};
// ---- std vs base64url alphabet distinctness ----
//
// [0xFB, 0xFF, 0xBF] hits the 62/63 alphabet slots: std emits '+'/'/',
// url emits '-'/'_'. The two encodings must differ, each round-trips
// under its own alphabet, and each is INVALID under the other (std
@@ -112,8 +106,6 @@ fn inval_check(enc: *base64.encoding, encoded: str) void = {
inval_check(&base64.url_encoding, s_std);
};
// ---- decodestr error cases ---- ref/hare/encoding/base64/base64.ha:525
//
// Table-driven (parallel-array idiom; tuple rows blocked by #111). Each
// row exercises one malformed class the hand-written validation in
// decodestr must reject. Cross-alphabet chars are covered separately by
@@ -142,8 +134,6 @@ fn inval_check(enc: *base64.encoding, encoded: str) void = {
};
};
// ---- size calc ---- ref/hare/encoding/base64/base64.ha:601
@test fn sizes() void = {
assert(!(base64.encodedsize(0) != 0));
assert(!(base64.encodedsize(1) != 4));
@@ -172,8 +162,6 @@ fn inval_check(enc: *base64.encoding, encoded: str) void = {
base64.decodedsize(5i32);
};
// ---- round-trip every byte value 0..255 (std + url) ----
@test fn roundtrip_all_bytes() void = {
let src: [256]u8;
let i: i32 = 0;

View File

@@ -1,8 +1,6 @@
// hextest — exercises lib/encoding/hex's io-streaming surface. Run with
// `out/bin/ww run lib/encoding/hex/hextest.ww`. Mirrors Hare's hex
// @test fns (ref/hare/encoding/hex/hex.ha:82,96,194) plus a full-byte
// round-trip. A failing row aborts via the assert/abort builtin (task #5
// @test conversion).
// Mirrors Hare's hex @test fns (ref/hare/encoding/hex/hex.ha:82,96,194)
// plus a full-byte round-trip. A failing row aborts via the assert/abort
// builtin (task #5 @test conversion).
//
// The streaming decoder (newdecoder) is deferred (#247), so the decode
// side is exercised through decodestr only.
@@ -33,8 +31,6 @@ fn cafebabe() [8]u8 = {
return r;
};
// ---- encodestr ---- ref/hare/encoding/hex/hex.ha:82
@test fn encodestr_basic() void = {
let in: [8]u8 = cafebabe();
assert(!(!streq(hex.encodestr(in[0:8]), "cafebabedeadf00d")));
@@ -57,8 +53,6 @@ fn cafebabe() [8]u8 = {
assert(!(hex.encodestr(in[0:0]).len != 0));
};
// ---- encode (io.handle sink) ---- ref/hare/encoding/hex/hex.ha:96
@test fn encode_stream() void = {
let in: [8]u8 = cafebabe();
let out: memio.stream = memio.dynamic();
@@ -69,8 +63,6 @@ fn cafebabe() [8]u8 = {
assert(!(!streq(memio.string(&out), "cafebabedeadf00d")));
};
// ---- decodestr round-trip ---- ref/hare/encoding/hex/hex.ha:194
@test fn decodestr_lower() void = {
match (hex.decodestr("cafebabedeadf00d")) {
case let b: []u8 => {
@@ -110,9 +102,8 @@ fn cafebabe() [8]u8 = {
};
};
// ---- decodestr error cases ---- ref/hare/encoding/hex/hex.ha:154,199
//
// Odd length and non-hex chars both return errors.invalid.
// Odd length and non-hex chars both return errors.invalid
// (ref/hare/encoding/hex/hex.ha:154,199).
@test fn decodestr_odd() void = {
match (hex.decodestr("abc")) {
@@ -135,8 +126,6 @@ fn cafebabe() [8]u8 = {
};
};
// ---- round-trip every byte value 0..255 --------------------------------
@test fn roundtrip_all_bytes() void = {
let src: [256]u8;
let i: i32 = 0;

View File

@@ -1,7 +1,5 @@
// decodetest — exercises the utf8 decoder: next/prev/validate/
// remaining/slice/position, plus the encode→decode round-trip. A
// failing row aborts via the assert/abort builtin (task #5 @test
// conversion). Vectors mirror ref/hare/encoding/utf8/decode.ha.
// Vectors mirror ref/hare/encoding/utf8/decode.ha. A failing row aborts
// via the assert/abort builtin (task #5 @test conversion).
package utf8_test;

View File

@@ -1,6 +1,5 @@
// encodetest — exercises utf8.encoderune. A failing row aborts via
// the assert/abort builtin (task #5 @test conversion).
// Vectors mirror ref/hare/encoding/utf8/encode.ha.
// Vectors mirror ref/hare/encoding/utf8/encode.ha. A failing row aborts
// via the assert/abort builtin (task #5 @test conversion).
package utf8_test;

View File

@@ -1,6 +1,5 @@
// runetest — exercises utf8.runesz/utf8sz. A failing row aborts via
// the assert/abort builtin (task #5 @test conversion).
// Vectors mirror ref/hare/encoding/utf8/rune.ha.
// Vectors mirror ref/hare/encoding/utf8/rune.ha. A failing row aborts
// via the assert/abort builtin (task #5 @test conversion).
package utf8_test;

View File

@@ -1,6 +1,5 @@
// typestest — exercises utf8.strerror. A failing row aborts via the
// Mirrors ref/hare/encoding/utf8/types.ha. A failing row aborts via the
// assert/abort builtin (task #5 @test conversion).
// Mirrors ref/hare/encoding/utf8/types.ha.
package utf8_test;

View File

@@ -1,4 +1,4 @@
// encoding/utf8 — UTF-8 encode/decode. Hare port; see
// Hare port; see
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
//
// The decoder is Hoehrmann's branchless DFA, originally published
@@ -453,4 +453,3 @@ export fn slice(begin: *decoder, end: *decoder) []u8 = {
export fn position(d: *decoder) i32 = {
return d.offs: i32;
};

View File

@@ -1,7 +1,7 @@
// endian — byte-order conversions. Mirrors Hare's endian:: surface:
// big-endian (be*), little-endian (le*), and network-order (hton/ntoh)
// helpers. Host order on amd64 is little-endian, so hton/ntoh are
// byte swaps and the le* family is identity.
// Mirrors Hare's endian:: surface: big-endian (be*), little-endian
// (le*), and network-order (hton/ntoh) helpers. Host order on amd64 is
// little-endian, so hton/ntoh are byte swaps and the le* family is
// identity.
package endian;

View File

@@ -1,6 +1,3 @@
// errnotest — exercises [[errors.errno]] and the [[errors.opaque_]]
// tail. Run with `out/bin/ww run lib/errors/errnotest.ww`.
//
// Parallel `[N]T` arrays of (errno, expected-tag) rather than a
// `[N]struct{...}` table — the cstage cgen's chained `arr[i].field`
// store gap (task #6). [[errtag]] collapses each named-void variant to
@@ -42,7 +39,6 @@ fn errtag(e: errors.error) int = {
};
};
// ---- errno → named condition --------------------------------------
@test fn mapping() void = {
let ins: [12]os.errno;
ins[0] = os.ECONNREFUSED;
@@ -85,7 +81,6 @@ fn errtag(e: errors.error) int = {
};
};
// ---- unmapped errno → opaque_ tail --------------------------------
@test fn opaquetail() void = {
// EIO (5) is outside the mapped set, so it wraps opaque_.
let e: errors.error = errors.errno(5);
@@ -98,7 +93,6 @@ fn errtag(e: errors.error) int = {
};
};
// ---- os.strerror mapped path --------------------------------------
@test fn strerrortext() void = {
assert(!(!streq(os.strerror(os.ENOENT), "No such file or directory")));
assert(!(!streq(os.strerror(os.EINVAL), "Invalid argument")));

View File

@@ -1,23 +1,6 @@
// fmt — formatting writers. Mirrors Hare's lib/fmt subset. Project #94
// fold-eFinal; io fold-2 (#5) graduated the sink to [[io.handle]].
//
// fprint / fprintln / fprintf / fprintfln
// write to an [[io.handle]] (= `(io.file |
// io.stream)`) — Hare's primary surface
// (ref/hare/fmt/print.ha:13). Errors via
// [[io.error]]. A file handle (raw fd) and a stream
// (`*io.vtable`) both flow in; [[io.write]]
// dispatches per arm.
//
// The process-stdio wrappers (print/println/errorln/printf/printfln/
// errorfln/fatal/fatalf) route through the fprint family over a file
// handle built from os.STD{OUT,ERR}_FILENO. The #5 handle convergence
// retired the prior `fd_ctx` shim — the fake-stream vtable around
// os.write that stood in before io.write took a handle.
//
// 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 (primary
// surface ref/hare/fmt/print.ha:13). Project #94 fold-eFinal; io
// fold-2 (#5) graduated the sink to [[io.handle]].
package fmt;
@@ -90,11 +73,6 @@ fn i64dec(v: i64) str = {
// construction — both stages now accept bare int by MEMBERSHIP.
export type formattable = (i64 | str | bool | rune | f64 | int | uint);
// ---- internal stream formatters --------------------------------------
// putbytes — io.write(s, [ptr..ptr+n)). Internal helper; the inline
// `let v` slice synthesis composes the (ptr, len) triple each
// formattable arm carries.
fn putbytes(s: io.handle, p: *u8, n: i32) (size | io.error) = {
let v: []u8;
v.ptr = p;
@@ -102,7 +80,6 @@ fn putbytes(s: io.handle, p: *u8, n: i32) (size | io.error) = {
return io.write(s, v);
};
// writeone — emit one formattable through io.write.
fn writeone(s: io.handle, a: formattable) (size | io.error) = {
match (a) {
case let n: i64 => {
@@ -145,8 +122,6 @@ fn writeone(s: io.handle, a: formattable) (size | io.error) = {
return 0: size;
};
// ---- {n}-placeholder parser -----------------------------------------
//
// Mirrors ref/hare/fmt/{iter,print,wrappers}.ha. Format sequences:
//
// {} implicit-positional next arg
@@ -636,8 +611,6 @@ fn formatfield(s: io.handle, f: field, m: *mods) (size | io.error) = {
};
};
// ---- stream sinks ----------------------------------------------------
// fprint — write the formatted form of each `args` element to `s`,
// separated by spaces. Returns total bytes written or the first
// io.error. Mirrors ref/hare/fmt/print.ha (fprint) + wrappers.ha.
@@ -818,9 +791,7 @@ export fn asprintf(fmt: str, args: field...) str = {
return strings.frombytes(tight);
};
// ---- process-stdio wrappers -----------------------------------------
//
// Mirror ref/hare/fmt/wrappers.ha. Hare routes these through
// Mirror ref/hare/fmt/wrappers.ha. Hare routes the stdio wrappers through
// os::stdout / os::stderr (io::handle); ww's os plays the sys role and
// can't import io (import floor), so it exports the std fd NUMBERS
// (os.STD{OUT,ERR}_FILENO) and the io.file binding is cast at the call

View File

@@ -1,6 +1,3 @@
// 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
@@ -24,10 +21,6 @@ fn streq(a: str, b: str) bool = {
return true;
};
// ---- an error-returning stream for the io.error surfacing test --------
//
// Replaces the OLD io.closed source. errvt is module-static; its
// reader/writer return a nomem-widened io.error.
let errvt: io.vtable;
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
@@ -42,8 +35,6 @@ fn errsource() io.stream = {
return &errvt;
};
// ---- fprint: bare str --------------------------------------------------
@test fn fprintbarestr() void = {
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
@@ -59,8 +50,6 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprint: int + str mix, space-separated ----------------------------
@test fn fprintintstr() void = {
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
@@ -76,8 +65,7 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprint: i64::MIN / int MIN whole-magnitude (#67) ------------------
// i64dec used `n = -n` within i64, which wraps at i64::MIN (-MIN == MIN),
// #67: i64dec used `n = -n` within i64, which wraps at i64::MIN (-MIN == MIN),
// so the digit loop never ran and only the bare '-' was emitted. The fix
// takes the magnitude through math.absi64 into u64. These rows pin the
// boundary value plus a normal negative, zero, and int MIN.
@@ -114,8 +102,6 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprint: bool + rune renders as "true A" --------------------------
@test fn fprintboolrune() void = {
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
@@ -131,8 +117,6 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprint: zero args returns 0, no bytes written --------------------
@test fn fprintempty() void = {
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
@@ -148,8 +132,6 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprintln: multi-arg, trailing '\n' --------------------------------
@test fn fprintlnmulti() void = {
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
@@ -165,8 +147,6 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprintln: zero args writes just the newline ----------------------
@test fn fprintlnempty() void = {
let mem: memio.stream = memio.dynamic();
let s: io.stream = &mem.vt;
@@ -182,7 +162,6 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- 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.error. Verifies the inner-loop arithmetic adds the actual byte
@@ -204,7 +183,6 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprint over a closed stream surfaces io.error -------------------
// 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
@@ -220,10 +198,7 @@ fn errsource() io.stream = {
};
};
// ---- fprintf scenarios -------------------------------------------------
// Each scenario writes through memio.dynamic and compares bytes against
// an inline `want`. Variadic call-site shape forces one body per shape;
// see the file header.
// Variadic call-site shape forces one body per shape; see the file header.
@test fn fprintf_implicit() void = {
let mem: memio.stream = memio.dynamic();
@@ -303,8 +278,7 @@ fn errsource() io.stream = {
match (c) { case void => {}; case let eioe: io.error => abort(); };
};
// ---- fprint: rune args emit full UTF-8, not a truncated byte (#66) -----
// writeone/formatraw's rune arm did `buf[0] = r: u8; putbytes(...,1)`,
// #66: writeone/formatraw's rune arm did `buf[0] = r: u8; putbytes(...,1)`,
// emitting only the low byte (invalid UTF-8 for r > 0x7F). The fix routes
// through utf8.encoderune (ref/hare/fmt/print.ha:84). Edge runes: é (2B),
// € (3B), 😀 (4B), A (1B).
@@ -558,8 +532,6 @@ fn errsource() io.stream = {
os.free(r.ptr: *void, r.len: u64);
};
// ---- f64 dispatch arm -------------------------------------------------
//
// Pins the f64 formattable arm landed under task #17 (unblocked by #30
// — the variant-widen-from-X0 fix). strconv.f64tos drives the render;
// see lib/strconv/strconv.ww for the documented subset (fixed-point,

View File

@@ -2,12 +2,6 @@
// fnmatch:: (ref/hare/fnmatch/fnmatch.ha) using ww byte-indexed
// cursors in place of Hare's strings::iterator.
//
// Surface today:
//
// fnmatch.flag — enum i32 bitmask (NONE / PATHNAME /
// NOESCAPE / PERIOD)
// fnmatch.fnmatch(pattern: str, string: str, flags: flag) bool
//
// Matching rules (Hare-spec):
//
// - '?' matches any single byte
@@ -328,7 +322,6 @@ fn fnmatch_internal(pattern: str, string: str, fl: flag) (bool | invalid) = {
let pp: i32 = 0;
let sp: i32 = 0;
// ---- prefix: match up to the first '*' ----------------------
let sawstar: bool = false;
for (!sawstar) {
let scur: i32 = sp;
@@ -361,7 +354,6 @@ fn fnmatch_internal(pattern: str, string: str, fl: flag) (bool | invalid) = {
if (advance) { sp = scur; };
};
// ---- find the tail (token count after the last '*') ---------
let pp_copy: i32 = pp;
let pp_last: i32 = pp;
let pp_last_cnt: i32 = 0;
@@ -398,7 +390,6 @@ fn fnmatch_internal(pattern: str, string: str, fl: flag) (bool | invalid) = {
let tail_pos: i32 = string.len - tail_cnt;
if (tail_pos < sp_copy) { return false; };
// ---- tail: match (from pp) against string[tail_pos..] -------
let ts: i32 = tail_pos;
let tdone: bool = false;
for (!tdone) {
@@ -439,7 +430,6 @@ fn fnmatch_internal(pattern: str, string: str, fl: flag) (bool | invalid) = {
};
};
// ---- middle: greedy match of each star-delimited subpattern --
let mid_pat: str;
mid_pat.ptr = pattern.ptr + (pp_copy: u64);
mid_pat.len = pp_last - pp_copy;
@@ -525,7 +515,6 @@ fn fnmatch_pathname(pattern: str, string: str, fl: flag) (bool | invalid) = {
let final: bool = false;
for (!final) {
segstart_pat = pp;
// Walk pattern until the next '/' (segment break) or end.
let inner: bool = true;
let kind: i32 = 0; // 0 = saw '/', 1 = saw end
for (inner) {

View File

@@ -1,6 +1,3 @@
// fnmatchtest — exercises lib/fnmatch. Run with
// `out/bin/ww run lib/fnmatch/fnmatchtest.ww`.
//
// Table-driven via the [[check]] helper: every row is the uniform
// 4-tuple `(pattern, string, expected, flags)`. Cases follow Hare's
// ref/hare/fnmatch/+test.ha (the de-facto spec for this port);
@@ -23,8 +20,6 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
assert(!(fnmatch.fnmatch(pat, s, f) != expected));
};
// ---- basic literal / wildcard cases ---------------------------------
@test fn basic() void = {
check("a", "a", true, 0);
check("b", "b", true, 0);
@@ -59,8 +54,6 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
check("*?*?*?*?", "abc", false, 0);
};
// ---- bracket expressions: literal / range / negation ----------------
@test fn brackets() void = {
check("[b]", "b", true, 0);
check("a[b]c", "abc", true, 0);
@@ -110,8 +103,6 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
check("[!-ac]", "b", true, 0);
};
// ---- POSIX character classes: [[:alnum:]] / [[:alpha:]] / ... -------
@test fn ctype() void = {
check("[[:alnum:]]", "7", true, 0);
check("[[:alpha:]]", "[", false, 0);
@@ -140,8 +131,6 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
check("[![:alnum:][:digit:]]", "a", false, 0);
};
// ---- flag.PERIOD: leading '.' must be literal in the pattern --------
@test fn period() void = {
let fp: i32 = 4; // flag.PERIOD
check(".", ".", true, fp);
@@ -152,16 +141,12 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
check(".*", "asdf", false, fp);
};
// ---- flag.NOESCAPE: '\\' loses its escape meaning -------------------
@test fn noescape() void = {
let nesc: i32 = 2; // flag.NOESCAPE
check("\\", "\\", true, nesc);
check("\\*", "\\asdf", true, nesc);
};
// ---- musl-adapted cases (no flags) ---------------------------------
@test fn musl_basic() void = {
check("*.c", "foo.c", true, 0);
check("*.c", ".c", true, 0);
@@ -201,8 +186,6 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
check("[![:d-d]", "[", false, 0);
};
// ---- flag.PATHNAME: '/' must be matched by literal '/' --------------
@test fn pathname() void = {
let fp: i32 = 1; // flag.PATHNAME
check("[a-z]/[a-z]", "a/b", true, fp);
@@ -217,8 +200,6 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
check("???b", "aa/b", false, fp);
};
// ---- flag.PERIOD + flag.PATHNAME combined ---------------------------
@test fn combined() void = {
let pp: i32 = 1; // PATHNAME
let fp: i32 = 4; // PERIOD

View File

@@ -403,8 +403,6 @@ export fn strerror(err: *error) str = {
return r;
};
// ---- help output --------------------------------------------------------
//
// ref/hare/getopt/getopts.ha:202-335.
//
// Divergences from Hare (all forced):
@@ -447,13 +445,11 @@ fn _printusage(
) (size | io.error) = {
let z: size = 0;
// "Usage: <name>"
match (fmt.fprint(out, "Usage:", name)) {
case let n: size => z += n;
case let e: io.error => return e;
};
// Optional auto-[-h] + flag cluster [-Xabc].
let startedflags: bool = false;
if (!hascmdh) {
match (fmt.fprint(out, " [-h")) {
@@ -485,7 +481,6 @@ fn _printusage(
};
};
// Parameter slots [-X <name>].
i = 0;
for (i < help.len) {
let p: *help = &help[i];
@@ -571,7 +566,6 @@ export fn printhelp(
return void;
};
// Print "name: summary\n\n" if help[0] is a CMD entry.
let p0: *help = &help[0];
if (p0.kind == helpkind.CMD) {
match (fmt.fprintfln(out, "{}: {}\n", name, p0.text)) {
@@ -593,13 +587,11 @@ export fn printhelp(
case let e: io.error => return e;
};
// Blank line between usage and option list.
match (fmt.fprint(out, "\n")) {
case let n: size => {};
case let e: io.error => return e;
};
// Auto -h line if not declared.
if (!hascmdh) {
match (fmt.fprintln(out, "-h: print this help text")) {
case let n: size => {};
@@ -607,7 +599,6 @@ export fn printhelp(
};
};
// Per-option lines.
let i: i32 = 0;
for (i < help.len) {
let p: *help = &help[i];

View File

@@ -1,6 +1,3 @@
// getopttest — exercises lib/getopt. Run with
// `out/bin/ww run lib/getopt/getopttest.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
@@ -36,8 +33,6 @@ fn streq(a: str, b: str) bool = {
return true;
};
// ---- flagcluster: -Fahs files.txt → 4 flags + 1 arg --------------------
@test fn flagcluster() void = {
let helps: [5]getopt.help;
getopt.cmdhelp(&helps[0], "list files");
@@ -82,8 +77,6 @@ fn streq(a: str, b: str) bool = {
getopt.finish(&cmd);
};
// ---- paramflag: glued + separated arguments ----------------------------
@test fn paramflag() void = {
let helps: [3]getopt.help;
getopt.cmdhelp(&helps[0], "edit");
@@ -125,8 +118,6 @@ fn streq(a: str, b: str) bool = {
getopt.finish(&cmd);
};
// ---- separator: -- ends option processing ------------------------------
//
// `--` in three positions: after a flag, immediately after argv[0],
// and at the tail with nothing trailing. Flat-packed argv per
// errortable's pattern; rows expect distinct (optslen, argslen,
@@ -176,8 +167,6 @@ fn streq(a: str, b: str) bool = {
};
};
// ---- repeatedflag: -vvv → 3 v opts -------------------------------------
@test fn repeatedflag() void = {
let helps: [2]getopt.help;
getopt.cmdhelp(&helps[0], "verbose count");
@@ -207,8 +196,6 @@ fn streq(a: str, b: str) bool = {
getopt.finish(&cmd);
};
// ---- baredash: "-" alone is a positional --------------------------------
//
// Bare `-` in three positions: trailing after a flag, leading (which
// halts option scanning), and followed by what looks like a flag
// (also halts — first non-flag wins).
@@ -257,8 +244,6 @@ fn streq(a: str, b: str) bool = {
};
};
// ---- errortable: unknownopt + requiresarg variants ---------------------
//
// Parallel arrays per row: argv flat-packed into `srcs`, with `argo`
// the offset and `argn` the count for each row.
@test fn errortable() void = {
@@ -306,8 +291,6 @@ fn streq(a: str, b: str) bool = {
};
};
// ---- strerrortext: render both error kinds -----------------------------
//
// Parallel rows over (kind, flag, name) → expected message. `kind`
// rides as i32 because the errorkind enum doesn't yet index an
// array element directly.
@@ -332,8 +315,6 @@ fn streq(a: str, b: str) bool = {
};
};
// ---- nooptionsbare: argv with only program name + positionals ----------
//
// Positional-only argv at three lengths: two positionals, one
// positional, and the program-name-only edge.
@test fn nooptionsbare() void = {
@@ -380,8 +361,6 @@ fn streq(a: str, b: str) bool = {
};
};
// ---- printusage_cases: table of width-measure paths --------------------
//
// ref/hare/getopt/getopts.ha:202-265.
// Row 0: short line (≤72) — auto-[-h] + FLAG cluster, no wrap.
// Row 1: long line (>72) — PARAM slots each prefixed with \n\t.
@@ -434,8 +413,6 @@ fn streq(a: str, b: str) bool = {
};
};
// ---- optionsizepin: option is 32B, not 24B -----------------------------
//
// tryparse/finish free the parsed-option array at `cap * size(option)`.
// option = rune (4) + pad (4) + str (24, #1/Phase 3) = 32B. A hardcoded
// `24u64` here was an 8-byte-per-element under-free; pinning the size
@@ -445,8 +422,6 @@ fn streq(a: str, b: str) bool = {
assert(!(size(getopt.option) != 32));
};
// ---- freeroundtrip: parse → finish over {0, 1, several} options --------
//
// Exercises the free path at three distinct cap values so the
// `cap * size(option)` free in [[finish]] runs for an empty, single,
// and multi-element option array. The under-free is not directly
@@ -493,8 +468,6 @@ fn streq(a: str, b: str) bool = {
};
};
// ---- printhelp_cases: table of help-output paths ----------------------
//
// ref/hare/getopt/getopts.ha:281-314.
// Row 0: empty help slice → early return, no output.
// Row 1: full help (CMD+FLAG+PARAM+CMD) → summary + usage + option list.

View File

@@ -1,4 +1,4 @@
// hash/adler32 — Adler-32 checksum (RFC 1950). Pure ww.
// Adler-32 checksum (RFC 1950).
//
// Hare ships a hash::hash-shaped streaming type backed by io::stream;
// we ship the pure-buffer subset here, same shape as lib/hash/fnv.

View File

@@ -1,5 +1,3 @@
// hash/crc16 — CRC-16 checksum. Pure ww.
//
// Inline polynomial-shift per byte (no precomputed tables). Slower
// than a table-driven CRC by ~8x per byte but matches the
// table-driven answer bit-for-bit.
@@ -15,9 +13,7 @@ def ANSI: u16 = 0xA001u16; // Modbus, USB, ANSI X3.28
// sum16 — fold `buf` under `poly` and return ~cval. Initial value is
// ~0u16, matching the streaming CRC-16 contract for a single
// write-then-sum. Per byte: XOR low byte of cval with msg byte to form
// an 8-bit index, run 8 polynomial shifts on that index, XOR the
// result with the high byte of cval shifted down.
// write-then-sum.
export fn sum16(buf: []u8, poly: u16) u16 = {
let c: u16 = 0xFFFFu16;
let i: i32 = 0;

View File

@@ -1,5 +1,3 @@
// hash/crc32 — CRC-32 checksum. Pure ww.
//
// Same shape as lib/hash/crc16: per-byte inline polynomial shift,
// no precomputed table. Slower than Hare's table-driven path by ~8x
// per byte but produces identical answers.
@@ -10,9 +8,7 @@ def IEEE: u32 = 0xEDB88320u32; // gzip, PNG, zip, Ethernet
def CASTAGNOLI: u32 = 0x82F63B78u32; // iSCSI, SCTP, SSE4.2
def KOOPMAN: u32 = 0xEB31D82Eu32; // small datasets
// sum32 — fold `buf` under `poly` (reversed form). Initial cval is
// ~0u32; per byte we mix in the low byte via 8 polynomial shifts and
// XOR with the high three bytes shifted down.
// sum32 — fold `buf` under `poly` (reversed form). Initial cval is ~0u32.
export fn sum32(buf: []u8, poly: u32) u32 = {
let c: u32 = 0xFFFFFFFFu32;
let i: i32 = 0;

View File

@@ -1,5 +1,3 @@
// hash/crc64 — CRC-64 checksum. Pure ww.
//
// Same shape as lib/hash/crc32: per-byte inline polynomial shift, no
// precomputed table. Slower than Hare's table-driven path by ~8x per
// byte but produces identical answers for the documented polynomials.
@@ -11,9 +9,7 @@ package crc64;
def ECMA: u64 = 0xC96C5795D7870F42u64; // ECMA-182, xz-utils
def ISO: u64 = 0xD800000000000000u64; // ISO 3309 HDLC
// sum64 — fold `buf` under `poly` (reversed form). Initial cval is
// ~0u64; per byte we mix in the low byte via 8 polynomial shifts and
// XOR with the high seven bytes shifted down.
// sum64 — fold `buf` under `poly` (reversed form). Initial cval is ~0u64.
export fn sum64(buf: []u8, poly: u64) u64 = {
let c: u64 = 0xFFFFFFFFFFFFFFFFu64;
let i: i32 = 0;

View File

@@ -1,5 +1,3 @@
// hash/fnv — FNV-1a 64-bit. Pure ww. No dependencies.
package fnv;
def OFFSET: u64 = 14695981039346656037;

View File

@@ -1,5 +1,3 @@
// hash/siphash — SipHash-2-4 keyed hash, buffer-based.
//
// Mirrors Hare's hash::siphash for the one-shot path: take a 16-byte
// key and a buffer, return the 64-bit hash. Hare ships a streaming
// io::stream-backed type; ww's subset doesn't, matching the rest of

View File

@@ -5,19 +5,13 @@
// through it. The error channel is the return value — Hare-shaped
// tagged unions instead of errno-style integer sentinels.
//
// This file owns the eof / underread variant tags; lib/io/stream.ww
// owns the `vtable` + `stream` + read/write/close dispatchers,
// lib/io/empty.ww owns the [[empty]] singleton, and lib/io/types.ww
// owns the error union, mode/whence enums, and the reader/writer/
// closer fn-type aliases.
// #94 fold-eFinal collapsed the pre-vtable `stream` struct + `closed`
// tag into the single vtable surface; the dispatchers are read/write/
// close (over `stream`), final over `handle` at io fold-2 (#5).
package io;
// eof — read past the end of the stream. Hare uses the `done`
// singleton for EOF; ww doesn't have `done` yet so we ship a
// named-void variant tag. Lifts to `done` with #93.
// Hare uses the `done` singleton for EOF; ww doesn't have `done` yet
// so we ship a named-void variant tag. Lifts to `done` with #93.
export type eof = void;
// underread — an I/O handle hit eof partway through a fixed-size

View File

@@ -89,8 +89,7 @@ fn st_close(s: stream) (void | error) = {
};
};
// ref/hare/io/stream.ha:70-77. Clone of st_read's slot-dispatch shape
// over the new `seeker` vtable slot.
// ref/hare/io/stream.ha:70-77.
fn st_seek(s: stream, off: off, w: whence) (off | error) = {
match (s.seeker) {
case void => {
@@ -102,9 +101,8 @@ fn st_seek(s: stream, off: off, w: whence) (off | error) = {
};
};
// ref/hare/io/handle.ha:16-23. The handle-typed public read: the
// file-arm routes to os.read (Q2 layering), the stream-arm delegates to
// the unchanged st_read vtable dispatch above.
// ref/hare/io/handle.ha:16-23. The file arm routes to os.read
// directly (the Q2 layering ruling: os stays below io).
export fn read(h: handle, buf: []u8) (size | eof | error) = {
match (h) {
case let fd: file => {

View File

@@ -1,14 +1,8 @@
// types — error union, mode/whence enums, reader/writer/closer
// fn-type aliases. Project #94 fold-eFinal; the fn-aliases target
// `stream` (= `*vtable`, the single io surface).
//
// Hare splits the io module across stream.ha + types.ha and ww does
// the same: lib/io/io.ww owns the `eof` and `underread` tags;
// lib/io/stream.ww owns the vtable surface (`vtable`, `stream`,
// `read`/`write`/`close` dispatch); this file owns the surrounding
// port. The four files share `package io;` so cross-file refs
// resolve via the dir-enum concat order (io.ww < stream.ww < types.ww
// — `stream` lands before the reader/writer/closer aliases below).
// the same (#94 fold-eFinal). The four files share `package io;` so
// cross-file refs resolve via the dir-enum concat order (io.ww <
// stream.ww < types.ww — `stream` lands before the reader/writer/
// closer aliases below).
//
// Deferrals (drew-signed): `copier`, `strerror` (the #5 list's
// `handle` + `seeker` landed with the #5 arc and the memio-seeker

View File

@@ -2,24 +2,6 @@
// Subset of Hare's lib/log (ref/hare/log/{logger,funcs,global,silent}.ha).
// Project #94 fold-eFinal.
//
// Surface:
//
// log.logger — vtable with `println` + `printfln` slots
// log.stdlogger — first-field embed of logger + an io.stream sink
// log.new (sink: io.stream) stdlogger
// log.silent *logger — a logger that discards every record
// log.default *logger — a stdlogger writing to stderr
// log.global *logger — the dispatch target for [[println]] / [[fatal]]
// log.println (args: fmt.formattable...) void
// log.lprintln (log: *logger, args: fmt.formattable...) void
// log.printfln (format: str, fields: fmt.field...) void
// log.lprintfln (log: *logger, format: str, fields: fmt.field...) void
// log.fatal (args: fmt.formattable...) never
// log.lfatal (log: *logger, args: fmt.formattable...) never
// log.fatalf (format: str, fields: fmt.field...) never
// log.lfatalf (log: *logger, format: str, fields: fmt.field...) never
// log.setlogger (log: *logger) void
//
// VALUE-RETURN (Hare ref/hare/log/logger.ha:20): [[new]] builds a
// stdlogger in a local and `return r;` (proven field-by-field sret).
// stdlogger is 24B (two fn-ptrs + sink), returned via the SysV memory
@@ -111,7 +93,6 @@ export let silent: *logger;
export let default: *logger;
export let global: *logger;
// initdone — guards [[ensureinit]] so the one-shot wiring runs once.
let initdone: i32 = 0;
fn ensureinit() void = {
@@ -183,8 +164,6 @@ fn stdprintfln(l: *logger, format: str, fields: fmt.field...) void = {
fn silentprintln(l: *logger, args: fmt.formattable...) void = { };
fn silentprintfln(l: *logger, format: str, fields: fmt.field...) void = { };
// ---- public API ----------------------------------------------------------
// new — build a stdlogger over `sink`, returned BY VALUE. Mirrors
// ref/hare/log/logger.ha:20.
export fn new(sink: io.stream) stdlogger = {

View File

@@ -23,9 +23,7 @@ import memio;
assert(!(mem.pos != 0));
};
// The silent logger's printfln callback discards args without touching
// fmt or any sink. Same pattern as [[silentwritesnothing]] for the
// bare-args path.
// Same pattern as [[silentwritesnothing]] for the bare-args path.
@test fn silentignoresprintfln() void = {
let buf: [16]u8;
let mem = memio.fixed(buf[0:16]);

View File

@@ -1,8 +1,7 @@
// math/checked — overflow-checked integer arithmetic. Ported from
// Hare's math::checked (ref/hare/math/checked/checked.ha). add*/sub*/
// mul* return (result, overflow) with wrapping semantics. The saturating
// (clamp-on-overflow) siblings live in saturating.ww — same `checked`
// module.
// Ported from Hare's math::checked (ref/hare/math/checked/checked.ha).
// add*/sub*/mul* return (result, overflow) with wrapping semantics. The
// saturating (clamp-on-overflow) siblings live in saturating.ww — same
// `checked` module.
//
// Subset of Hare's surface (Hare splits per type, so these are clean
// omissions, not divergences):

View File

@@ -1,5 +1,4 @@
// math/checked (saturating) — clamp-on-overflow arithmetic. Ported from
// Hare's math::checked saturating siblings
// Ported from Hare's math::checked saturating siblings
// (ref/hare/math/checked/saturating.ha). Part of the same `checked`
// module as checked.ww. Each sat_* clamps to the type's range on
// overflow instead of wrapping.

View File

@@ -1,6 +1,4 @@
// floats — f64 classification, sign, bit-reinterpret core, and the f64
// decompose half (subnormal-normalize + frexp). Ported from
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
// Ported from ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
// issubnormalf64/normalizef64/frexpf64; strconv-foundation fold-1a:
// F32 bit-layout + f32bits/f32frombits + floatinfo struct type;
// fold-1b: NAN_BITS/INF_BITS sentinels; γ-cleanup: f64info/f32info
@@ -12,7 +10,6 @@
package math;
// Returns the binary representation of the given f64.
// ref/hare/math/floats.ha:5. Parens around &n are load-bearing: ww's `:`
// cast binds tighter than unary `&`, so Hare's `*(&n: *u64)` would parse
// as `*(&(n: *u64))`; `(&n): *u64` reinterprets the address as intended.
@@ -20,19 +17,16 @@ export fn f64bits(n: f64) u64 = {
return *((&n): *u64);
};
// Returns the binary representation of the given f32.
// ref/hare/math/floats.ha:8
export fn f32bits(n: f32) u32 = {
return *((&n): *u32);
};
// Returns f64 with the given binary representation.
// ref/hare/math/floats.ha:11
export fn f64frombits(n: u64) f64 = {
return *((&n): *f64);
};
// Returns f32 with the given binary representation.
// ref/hare/math/floats.ha:14
export fn f32frombits(n: u32) f32 = {
return *((&n): *f32);
@@ -45,25 +39,20 @@ export fn f32frombits(n: u32) f32 = {
// amounts and mask widths, so they are typed u64 here — the closest
// stand-in for Hare's untyped-int adapt at those use sites.
// The number of bits in the significand of the binary representation of f64.
export def F64_MANTISSA_BITS: u64 = 52;
// The number of bits in the exponent of the binary representation of f64.
export def F64_EXPONENT_BITS: u64 = 11;
// The bias of the exponent of the binary representation of f64. Subtract this
// from the exponent in the binary representation to get the actual exponent.
export def F64_EXPONENT_BIAS: u64 = 1023;
// Mask with each bit of an f64's mantissa set.
// ref/hare/math/floats.ha:37
export def F64_MANTISSA_MASK: u64 = (1 << F64_MANTISSA_BITS) - 1;
// Mask with each bit of an f64's exponent set.
// ref/hare/math/floats.ha:40
export def F64_EXPONENT_MASK: u64 = (1 << F64_EXPONENT_BITS) - 1;
// The mask that gets an f64's sign.
// ref/hare/math/floats.ha:75
def F64_SIGN_MASK: u64 = 1u64 << 63;
@@ -84,11 +73,9 @@ def F64_EXP_ZERO: u64 = (F64_EXPONENT_BIAS - 1) << F64_MANTISSA_BITS;
// the u32 bit container, the same way the F64 family rides u64 — see
// the note above F64_MANTISSA_BITS).
// The number of bits in the significand of the binary representation of f32.
// ref/hare/math/floats.ha:27
export def F32_MANTISSA_BITS: u32 = 23u32;
// The number of bits in the exponent of the binary representation of f32.
// ref/hare/math/floats.ha:30
export def F32_EXPONENT_BITS: u32 = 8u32;
@@ -97,15 +84,12 @@ export def F32_EXPONENT_BITS: u32 = 8u32;
// ref/hare/math/floats.ha:33
export def F32_EXPONENT_BIAS: u32 = 127u32;
// Mask with each bit of an f32's mantissa set.
// ref/hare/math/floats.ha:43
export def F32_MANTISSA_MASK: u32 = (1u32 << F32_MANTISSA_BITS) - 1u32;
// Mask with each bit of an f32's exponent set.
// ref/hare/math/floats.ha:46
export def F32_EXPONENT_MASK: u32 = (1u32 << F32_EXPONENT_BITS) - 1u32;
// The mask that gets an f32's sign.
// ref/hare/math/floats.ha:87
def F32_SIGN_MASK: u32 = 1u32 << 31;
@@ -127,15 +111,10 @@ def F32_EXP_ZERO: u32 = (F32_EXPONENT_BIAS - 1u32) << F32_MANTISSA_BITS;
// against ref/hare/strconv/stof.ha:248,288 (`let e: int = 0` arithmetic
// against `f.expbias` of the same type, no cast at use site).
export type floatinfo = struct {
// Bits in significand.
mantbits: u64,
// Bits in exponent.
expbits: u64,
// Bias of exponent.
expbias: int,
// Mask for mantissa.
mantmask: u64,
// Mask for exponent.
expmask: u64,
};
@@ -177,14 +156,12 @@ export def f32info: floatinfo = floatinfo {
export def NAN_BITS: u64 = 0x7FF8000000000000u64;
export def INF_BITS: u64 = 0x7FF0000000000000u64;
// Returns true if the given floating-point number is NaN.
// ref/hare/math/floats.ha:144 (Hare's expression body inlined into a
// block: ww has no expression-bodied fn form, only brace blocks).
export fn isnan(n: f64) bool = {
return n != n;
};
// Returns true if the given floating-point number is infinite.
// ref/hare/math/floats.ha:147
export fn isinf(n: f64) bool = {
const bits = f64bits(n);
@@ -193,7 +170,6 @@ export fn isinf(n: f64) bool = {
return exp == F64_EXPONENT_MASK && mant == 0;
};
// Returns true if the given f64 is subnormal.
// ref/hare/math/floats.ha:179
export fn issubnormalf64(n: f64) bool = {
const bits = f64bits(n);
@@ -202,7 +178,6 @@ export fn issubnormalf64(n: f64) bool = {
return exp == 0 && mant != 0;
};
// Returns the absolute value of f64 n.
// ref/hare/math/floats.ha:195
export fn absf64(n: f64) f64 = {
if (isnan(n)) {
@@ -222,19 +197,16 @@ export fn signf64(x: f64) i64 = {
};
};
// Returns whether or not x is positive.
// ref/hare/math/floats.ha:231
export fn ispositivef64(x: f64) bool = {
return signf64(x) == 1i64;
};
// Returns whether or not x is negative.
// ref/hare/math/floats.ha:237
export fn isnegativef64(x: f64) bool = {
return signf64(x) == -1i64;
};
// Returns x, but with the sign of y.
// ref/hare/math/floats.ha:243
export fn copysignf64(x: f64, y: f64) f64 = {
return f64frombits((f64bits(x) & ~F64_SIGN_MASK) |

View File

@@ -1,6 +1,6 @@
// math — numeric helpers. Subset of Hare's math::; only the absolute-
// value pair for the signed integer types we currently care about. The
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
// Subset of Hare's math::; only the absolute-value pair for the signed
// integer types we currently care about. The return type is unsigned
// so that abs(I32_MIN) doesn't overflow.
package math;

View File

@@ -9,11 +9,9 @@ package random;
export type random = u64;
// init — initialize a generator with `seed`. Same seed reproduces the
// same sequence on every run. Mirrors Hare's random::init.
// Mirrors Hare's random::init.
export fn init(seed: u64) random = { return seed: random; };
// next — return a pseudo-random 64-bit value and advance the state.
// SplitMix64, per Hare's random::next.
export fn next(r: *random) u64 = {
let s: u64 = (*r): u64 + 0x9E3779B97F4A7C15u64;

View File

@@ -122,8 +122,6 @@ export fn dynamicfrom(buf: []u8) stream = {
return r;
};
// ---- vtable callbacks ----------------------------------------------------
// readfn — recover the stream from the io.stream's `*vtable` via the
// intrusive offset-0 cast. Single fn over the common header (Hare's
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
@@ -247,11 +245,6 @@ fn dynamicgrow(d: *stream, need: i32) void = {
d.cap = newcap;
};
// ---- accessors over the common `stream` header -----------------------
//
// Single fn each: Hare's string/reset/buffer/borrowedread all take
// `*stream` and read the flat header.
// string — bytes written so far, as a str view (buf[0..pos]).
//
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns

View File

@@ -1,5 +1,3 @@
// 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
@@ -23,8 +21,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
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;
@@ -73,8 +69,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- fixedwritecases: full-fit / exact-fill / partial / overflow -------------
@test fn fixedwritecases() void = {
let dst: [16]u8;
let st: memio.stream = memio.fixed(dst[0:16]);
@@ -130,7 +124,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- fixedwritefull: the full-sink nomem contract (memio.ww:190) -------
// A write that exactly fills the sink succeeds; any further non-empty
// write into the full sink returns nomem, not a 0-byte success
// (ref/hare/memio/stream.ha:161). A zero-length sink is full from the
@@ -165,8 +158,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
};
};
// ---- dynamicgrowcases: every cap doubling exercised ---------------------
// Drive grow 0 → 8 → 16 → 32 by writing sized chunks. Verify
// accumulated `pos` after each step.
@test fn dynamicgrowcases() void = {
@@ -207,8 +198,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- 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 = {
@@ -255,8 +244,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- 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;
@@ -289,8 +276,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
};
};
// ---- stringview: len + endpoints track pos across appends ---------------
@test fn stringview() void = {
let st: memio.stream = memio.dynamic();
let s: io.stream = &st.vt;
@@ -327,8 +312,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- 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`
@@ -381,8 +364,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
match (c) { case void => {}; case let e: io.error => abort(); };
};
// ---- seekcases: SET/CUR/END × in-bounds/OOB over one fixed stream --------
// Sequential rows over one 8-byte fixed stream; CUR rows depend on the
// cursor left by earlier rows. want = resulting pos for valid rows,
// -1 = errors.invalid expected. After EVERY row an io.tell readback
@@ -467,8 +448,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
};
};
// ---- emptyseek: every whence over a 0-length buffer ----------------------
@test fn emptyseek() void = {
let arr: [1]u8;
arr[0] = 0u8;
@@ -505,8 +484,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
};
};
// ---- dynamicseek: rewind-and-reread over written data --------------------
// The dynamic vtable wires the same seekfn; bounds run against the
// LOGICAL length (m.len), not capacity (Hare: len(s.buf),
// ref/hare/memio/stream.ha:131,136).

View File

@@ -1,7 +1,5 @@
// net — minimal TCP. Sketched against the Linux syscall numbers
// 41 (socket), 42 (connect), 43 (accept), 49 (bind), 50 (listen).
// Real applications will want addrinfo + DNS; we leave that to
// higher layers.
// net — minimal TCP. Real applications will want addrinfo + DNS; we
// leave that to higher layers.
package net;

View File

@@ -1,6 +1,5 @@
// os — process and filesystem facade. The body of each call lands
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
// depending on how the program was linked.
// The body of each call lands either in libwwrt.a (rt_syscall
// trampoline) or libc bindings, depending on how the program was linked.
package os;
@@ -155,10 +154,8 @@ export fn close(fd: i32) i32 = {
return syscall1(nr.CLOSE, fd: i64): i32;
};
// dup2(2): make `newfd` refer to the same description as `oldfd`,
// closing `newfd` first if open. Returns `newfd` on success or a
// negative errno. Used by w6c_ww to redirect stdout into an output
// file without changing the cgen emit path.
// Used by w6c_ww to redirect stdout into an output file without
// changing the cgen emit path.
export fn dup2(oldfd: i32, newfd: i32) i32 = {
return syscall2(nr.DUP2, oldfd: i64, newfd: i64): i32;
};
@@ -208,9 +205,8 @@ export fn tryopen(path: str, flags: flag, mode: i32) (i32 | oserror) = {
return fd;
};
// lseek — set/inspect the fd's position. Returns the new offset or
// a negative errno. We use this for fstat-free file-size discovery
// (open ⇒ lseek to end ⇒ lseek back).
// Used for fstat-free file-size discovery (open ⇒ lseek to end ⇒
// lseek back).
export fn lseek(fd: i32, off: i64, w: whence) i64 = {
return syscall3(nr.LSEEK, fd: i64, off, (w as i32): i64);
};
@@ -273,7 +269,6 @@ export fn strerror(err: errno) str = {
return "Unknown error";
};
// filesize — byte length of an open fd via lseek-to-end-and-back.
export fn filesize(fd: i32) (i64 | oserror) = {
let end: i64 = lseek(fd, 0i64, whence.END);
if (end < 0) { return end: oserror; };
@@ -282,9 +277,8 @@ export fn filesize(fd: i32) (i64 | oserror) = {
return end;
};
// readall — keep reading until `n` bytes have arrived or the fd
// closes early. Hare name (io::readall); the buffer is caller-
// supplied, matching the Plan 9 subset convention.
// Hare name (io::readall); the buffer is caller-supplied, matching
// the Plan 9 subset convention.
export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
let got: u64 = 0u64;
for (got < n) {
@@ -296,8 +290,7 @@ export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
return got: i64;
};
// writeall — keep writing until `n` bytes have been accepted or the
// fd refuses progress. Hare name (io::writeall).
// Hare name (io::writeall).
export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
let sent: u64 = 0u64;
for (sent < n) {
@@ -309,8 +302,6 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
return sent: i64;
};
// ---- process and filesystem helpers used by the `ww` driver ----------
// access(2): returns 0 if the file is reachable, negative errno
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
@@ -531,8 +522,6 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64);
};
// ---- environment ------------------------------------------------------
// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW
// slot before calling main; this binding lifts the captured pointer
// into ww. Same FFI shape as rt_syscall / rt_malloc / rt_abort: a TEXT
@@ -688,8 +677,6 @@ export fn args() []str = {
return r;
};
// ---- stat / lstat / fstat / exists -----------------------------------
//
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
// ref/hare/sys/+linux/stat.ha:24-58). The Hare surface returns
// `filestat` by value; ww's cgreturn ABI tops out at 24B today (see
@@ -830,8 +817,7 @@ type kstat = struct {
let emptypath: [1]u8 = [0u8];
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
// filestat. Internal helper used by all three public entry points.
// Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
// filestat. Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
// newfstatat populates every field, so the mask is the OR-fold of
// all seven Hare stat_mask bits.
fn fillfilestat(out: *filestat, k: *kstat) void = {

View File

@@ -1,8 +1,6 @@
// ostest — exercises lib/os surface that doesn't have a dedicated
// test elsewhere. Covers [[os.getenv]] and a direct
// [[os.alloc]] / [[os.free]] roundtrip. memio's tests indirectly
// cover alloc/free; the direct row here pins the FFI shape under
// lib/os itself so future bindings refactors can't quietly drift.
// memio's tests indirectly cover alloc/free; the direct row here pins
// the FFI shape under lib/os itself so future bindings refactors can't
// quietly drift.
//
// getenv env contract (ww ships no setenv primitive, deliberately):
//
@@ -45,8 +43,6 @@ fn ostestwait(pid: i32, status: *i32) i32 = {
return -1;
};
// ---- getenv: set var → matching value -------------------------------
@test fn test_getenv_set() void = {
let r = os.getenv("WW_TEST_GETENV");
match (r) {
@@ -57,11 +53,9 @@ fn ostestwait(pid: i32, status: *i32) i32 = {
};
};
// ---- getenv: empty value (set but zero-length) ----------------------
//
// POSIX permits an env var with an empty value (`name=` in environ).
// getenv must return the empty str, NOT void — void is reserved for
// "name not present at all". This row exercises the boundary.
// "name not present at all".
@test fn test_getenv_empty() void = {
let r = os.getenv("WW_TEST_EMPTY");
@@ -73,8 +67,6 @@ fn ostestwait(pid: i32, status: *i32) i32 = {
};
};
// ---- getenv: unset var → void ---------------------------------------
@test fn test_getenv_unset() void = {
let r = os.getenv("WW_TEST_NOT_SET");
match (r) {
@@ -83,8 +75,6 @@ fn ostestwait(pid: i32, status: *i32) i32 = {
};
};
// ---- getenv: prefix-collision guard ---------------------------------
//
// Probes that "WW_TEST_GETEN" (a prefix of WW_TEST_GETENV) doesn't
// match. Without the explicit `entry[name.len] == '='` check in
// getenv, a naive prefix matcher would return the value of any
@@ -103,8 +93,6 @@ fn ostestwait(pid: i32, status: *i32) i32 = {
};
};
// ---- getenvs: direct non-vacuous coverage of the []str shape --------
//
// getenvs() is public API (task #28) and the single env walker getenv
// routes through. getenv's cases exercise the walk transitively, but
// none asserts the []str RETURN shape — its count or "NAME=VALUE" entry
@@ -139,8 +127,6 @@ fn ostestwait(pid: i32, status: *i32) i32 = {
};
};
// ---- alloc/free: mmap-backed runtime allocator ----------------------
//
// Direct round-trip. Write-then-read-back proves the returned page is
// dereferenceable. A miscompiled binding (wrong arg order, wrong ABI,
// etc.) would either fault or return zero here.

View File

@@ -1,7 +1,6 @@
// stattest — exercises [[os.stat]] / [[os.lstat]] / [[os.fstat]] /
// [[os.exists]] against a scratch tree each row arranges itself
// under [[temp.dir]] (Hare's own idiom: os tests self-arrange in a
// temp dir), so the suite passes bare with no external driver:
// Each row arranges its own scratch tree under [[temp.dir]] (Hare's
// own idiom: os tests self-arrange in a temp dir), so the suite
// passes bare with no external driver:
//
// <root>/regfile regular file, 11 bytes "hello world", 0644
// <root>/symlink → ./regfile (relative symlink)
@@ -66,14 +65,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
return ((m as u32) & 61440u32) == (t as u32);
};
// ---- stat: regular file --------------------------------------------
//
// Pinned bytes are "hello world" (11 bytes). We verify:
// - mask is fully set (newfstatat fills everything)
// - mode's type bits == REG
// - sz == 11
// - inode is non-zero (real fs entry, not synthetic)
@test fn test_stat_regfile() void = {
let t: tree;
mktree(&t);
@@ -112,8 +103,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
rmtree(&t);
};
// ---- stat: directory ------------------------------------------------
@test fn test_stat_subdir() void = {
let t: tree;
mktree(&t);
@@ -127,8 +116,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
rmtree(&t);
};
// ---- stat: missing path → oserror ENOENT ---------------------------
@test fn test_stat_noent() void = {
let t: tree;
mktree(&t);
@@ -143,11 +130,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
rmtree(&t);
};
// ---- stat (follow) vs lstat (no-follow) on a symlink ----------------
//
// stat follows the link → reports the regfile (REG, 11 bytes).
// lstat does NOT follow → reports the link itself (LINK).
@test fn test_stat_symlink_follow() void = {
let t: tree;
mktree(&t);
@@ -175,8 +157,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
rmtree(&t);
};
// ---- fstat: open a file and stat by fd ------------------------------
@test fn test_fstat_regfile() void = {
let t: tree;
mktree(&t);
@@ -194,8 +174,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
rmtree(&t);
};
// ---- exists: true on regfile/dir/symlink, false on noent ------------
@test fn test_exists_regfile() void = {
let t: tree;
mktree(&t);
@@ -217,8 +195,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
rmtree(&t);
};
// ---- ENAMETOOLONG: kpath rejects paths >= PATH_MAX -------------------
//
// kpath copies into a single [PATH_MAX]u8 buffer and reserves one byte
// for the NUL terminator (`p.len + 1 >= PATH_MAX` → reject). The
// rejection surfaces as `oserror = -36` (ENAMETOOLONG) on (... |

View File

@@ -152,8 +152,6 @@ export fn set(buf: *buffer, items: str...) (str | error) = {
return push(buf, items...);
};
// ---- c3 buffer-ops (ref/hare/path/buffer.ha, stack.ha) ----------------
// lifts Hare's fn-`static let buf` (buffer.ha:53-56): statically allocated,
// overwritten on the next local() call. strconv *tos-buf precedent.
let localbuf: [MAX]u8 = [0...];
@@ -225,7 +223,6 @@ export fn parent(buf: *buffer) (str | error) = {
return strings.frombytes(buf.buf[0:newend]);
};
// ---- POSIX jail (ref/hare/path/posix.ha) ------------------------------
// POSIX-compliant dirname/basename. They do NOT normalize the input and
// operate on plain str (no buffer), so they sit apart from the stack
// paradigm above — same POSIX-complaint as posix.ha:7-11.

View File

@@ -564,8 +564,6 @@ type fdexp = struct {
};
// ---- fold 3: anchors / escape / postfix / alternation ----------------
// instsig — flatten an inst for the table-driven program pins below:
// kind base + payload. Takes the 56B inst by value (the #19-landed
// is_consuming_inst shape).
@@ -821,8 +819,6 @@ type cerow = struct {
};
};
// ---- fold 4: bracket expressions --------------------------------------
// Emitted-program pins for the `[..]` arm: the charset inst lands where
// a literal would (and composes with fold-3's postfix/anchors), the
// charsets table grows one entry per bracket, negation rides
@@ -1173,8 +1169,6 @@ type cscase = struct {
};
};
// ---- fold 5a: capture groups ------------------------------------------
// The fold-5a compile-error surface, exact texts. ")" and "(" are
// Hare's own ERROR fixtures (+test.ha:276/606); "a("/"a)" graduate
// here from the metachar-loud table with their real texts. The
@@ -1464,8 +1458,6 @@ type smcase = struct {
};
};
// ---- fold 5b: repetition ----------------------------------------------
// 5b compile-error rows end-to-end through compile() — the two
// parse_repetition texts surface verbatim (+test.ha:461-463 ERROR
// rows). "a{" is the GRADUATED metachar-loud row: with `{` ported the
@@ -1693,8 +1685,6 @@ type smcase = struct {
};
};
// ---- fold 6: POSIX character classes ---------------------------------
// compile error: `[[:` with no valid class name
// ref/hare/regex/+test.ha — no direct cite; error string from
// regex.ha:203 "No character class after '[:'".

View File

@@ -2,14 +2,6 @@
// (ref/hare/shlex/{split.ha,escape.ha}) using ww byte-indexed cursors
// in place of Hare's strings::iterator.
//
// Surface today:
//
// 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.quotestr(s: str) str — quote into a fresh str
//
// Owning model: split() returns a fresh `[]str` of strings.dup'd
// elements; release with [[strings.freeall]] (Hare's natural disposer
// shape). quotestr() returns an os.alloc'd str; release via
@@ -227,7 +219,6 @@ export fn split(in: str) ([]str | syntaxerr) = {
dirty = true;
if (r == ' ' || r == '\t' || r == '\n') {
// Collapse a run of whitespace.
let inner: bool = true;
for (inner) {
if (pos >= in.len) { inner = false; }

View File

@@ -1,6 +1,3 @@
// shlextest — exercises lib/shlex. Run with
// `out/bin/ww run lib/shlex/shlextest.ww`.
//
// Two cohorts, grouped Hare-style — one @test fn per cohort, table-
// driven inside via per-arity helpers:
//
@@ -110,8 +107,6 @@ fn checkquote(in: str, expected: str) void = {
let _c: (void | io.error) = io.close(snk);
};
// ---- split: Hare's @test fn split() table --------------------------
//
// 9 success rows + 3 syntaxerr rows ported VERBATIM from
// ref/hare/shlex/+test.ha; plus one ww-specific edge (empty input →
// empty []str) confirmed by drew.
@@ -146,8 +141,6 @@ fn checkquote(in: str, expected: str) void = {
checkempty("");
};
// ---- quote: Hare's testquote rows + the empty-input edge ----------
//
// 4 rows from ref/hare/shlex/+test.ha @test fn quote(). The empty-
// input row (→ `''`) is implementation-specific (Hare's testquote
// doesn't cover it) but is documented behaviour per shlex.ww's
@@ -163,16 +156,12 @@ fn checkquote(in: str, expected: str) void = {
checkquote("@%+=:,./-", "@%+=:,./-");
};
// ---- quotestr ------------------------------------------------------
@test fn test_quotestr() void = {
let r: str = shlex.quotestr("hello world");
assert(!(!streq(r, "'hello world'")));
// leak r — short-lived test process, same precedent as fnmatchtest.
};
// ---- strerror ------------------------------------------------------
@test fn test_strerror() void = {
let e: shlex.syntaxerr;
let s: str = shlex.strerror(e);

View File

@@ -1,6 +1,5 @@
// strconv — arbitrary-precision decimal engine for float↔string
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
// Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports Go's
// lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
// references (#121 residual-guard SAFE).
//
// Spelling divergences from Hare (mechanical, ww-side parser shape):

View File

@@ -1,4 +1,3 @@
// strconv — float→string via Ryū (shortest round-trippable decimal).
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
// https://doi.org/10.1145/3192366.3192369 — Hare translated it from the
@@ -339,12 +338,12 @@ fn f64todecf64(mantissa: u64, exponent: u32) decf64 = {
return decf64 { exponent = (exp: i64), mantissa = output };
};
// ==== f32 Ryū sub-path (ftos_ryu.ha). The *32 helpers below mirror their
// f32 Ryū sub-path (ftos_ryu.ha). The *32 helpers below mirror their
// u64 siblings at 32-bit width; they reuse the SHARED f64computeinvpow5/
// f64computepow5 (and thus the f64 SPLIT2 tables) per ftos_ryu.ha — there
// is no separate f32 table. Same scalar-PARAM-mutation → copy-to-local,
// comma-split, expr-yield → block divergences as the f64 path
// above. ====
// above.
// ref/hare/strconv/ftos_ryu.ha:52. Largest p with 5^p | value (32-bit).
fn pow5fac32(v: u32) u32 = {
@@ -523,13 +522,13 @@ fn f32todecf32(mantissa: u32, exponent: u32) decf32 = {
return decf32 { mantissa = output, exponent = (exp: i64) };
};
// ==== G-format encode layer (ftos.ha) — only the ffmt::G / prec=void /
// G-format encode layer (ftos.ha) — only the ffmt::G / prec=void /
// fflags::NONE-REACHABLE logic. The SHOW_POINT/precision/E-vs-uppercase
// arms (ftos.ha:88-105, 127-145, 170-213's zeros/caps) are UNREACHABLE
// for G/void/NONE (ffpoint(NONE)=false, prec is never uint, f is always
// G) and are NOT ported — porting them stubbed would be untested dead
// code. The parametric ftosf/ffmt/fflags surface is deferred (task #64;
// needs a parametric consumer + io::handle + #158). ====
// needs a parametric consumer + io::handle + #158).
// ref/hare/strconv/ftos.ha:49. Decimal digit-count of n (n <= 1e17).
fn declen(n: u64) uint = {

View File

@@ -1,4 +1,3 @@
// strconv — Ryū float→string lookup tables + bit-count constants.
// Mirrors ref/hare/strconv/ftos_ryu.ha:159-222 byte-exact. Pure data
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
// f64computeinvpow5 / f64computepow5 (the Ryū power-of-five cores).

View File

@@ -1,6 +1,3 @@
// ftostest — exercises lib/strconv/ftos.ww (Hare ftos.ha / ftos_ryu.ha
// Ryū port).
//
// Ports ref/hare/strconv/+test/ftos_test.ha's ffmt::G / prec=void /
// fflags::NONE rows (the f64tos cases) verbatim — these are exactly
// strconv.f64tos's output. encode_f_dec is covered by 13.37/1100/0.011/…

View File

@@ -1,6 +1,3 @@
// inttest — exercises lib/strconv integer parse: parseint / stoi64 /
// stou64 / the iN/uN width wrappers.
//
// Verbatim port of ref/hare/strconv/stoi.ha:56-86 (stoi/stoi_bases) and
// stou.ha:116-138 (stou/stou_bases). Hare's strconv integer tests are
// flat assert SEQUENCES, not row-array tables — mirrored here as inline
@@ -210,7 +207,6 @@ fn ck_uint_ovf(id: i32, s: str, b: base) void = {
ck_uint(62, "110101", base.BIN, 53u64: uint); // 0b110101
};
// ---- format side: u64tos / i64tos / machine-word wrappers ------------
// Verbatim ports of ref/hare/strconv/utos.ha:74-103 (utos/utos_bases) and
// itos.ha:54-87 (itos/itos_bases) — Hare's format tests are flat assert
// sequences too. Radix-literal inputs are written in DECIMAL (ww value

View File

@@ -1,6 +1,6 @@
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
// Mirrors ref/hare/strconv/stof.ha (Hare in turn adapts Go):
// Eisel-Lemire fast path [1] with the Simple-Decimal-Conversion slow
// path [2] (decimal.ww) as fallback.
// [1]: https://nigeltao.github.io/blog/2020/eisel-lemire.html
// [2]: https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html
//

View File

@@ -1,5 +1,4 @@
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
// byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
// Mirrors ref/hare/strconv/stof_data.ha byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
// `leftshift_newdigits` lands (ref/hare/strconv/decimal.ha:35).
//

View File

@@ -1,5 +1,3 @@
// stoftest — exercises lib/strconv/stof.ww (Hare stof.ha port).
//
// Ports ref/hare/strconv/stof.ha's @test vectors (stof64 / stof32 /
// stofhex). Comparisons are BIT-level (math.f64bits / f32bits) so a
// sign flip (-0.0 vs 0.0) or a 1-ulp miss fails the row rather than
@@ -14,8 +12,6 @@ import strconv;
import math;
// ---- unwrap helpers (bit-exact value checks) -------------------------
fn chk64(s: str, b: base, want: f64) bool = {
match (stof64(s, b)) {
case let v: f64 => { return math.f64bits(v) == math.f64bits(want); };
@@ -89,7 +85,6 @@ fn nan32(s: str) bool = {
return false;
};
// ---- stof64 ----------------------------------------------------------
// ref/hare/strconv/stof.ha:530.
@test fn stof64_dec() void = {
@@ -141,7 +136,6 @@ fn nan32(s: str) bool = {
assert(!(!nan64("naN")));
};
// ---- stof32 ----------------------------------------------------------
// ref/hare/strconv/stof.ha:560.
@test fn stof32_dec() void = {
@@ -180,7 +174,6 @@ fn nan32(s: str) bool = {
assert(!(!chk32("9.19100241453305036800e+20", base.DEC, 9.19100241453305036800e+20f32)));
};
// ---- stofhex ---------------------------------------------------------
// ref/hare/strconv/stof.ha:590. Hex-float surface-form literals (0x1.fp-2,
// math::F64_MAX_NORMAL, …) are spelled as IEEE-754 bit patterns since ww
// has no hex-float literal lexer / no F*_MAX_NORMAL math consts.

View File

@@ -1,5 +1,3 @@
// strconv — number↔string conversions.
//
// Mirrors Hare's strconv:: surface. The *tos functions return a
// `const str` view into a module-level buffer that is overwritten on
// the next call to the same function; callers must copy the bytes if
@@ -71,7 +69,7 @@ let lut_lower: [16]rune = [
// fidelity; the initial value is irrelevant (only the freshly-written
// prefix is ever read) but the fill form is exercised (probed: emits
// byte-identically cross-stage).
let u64tos_buf: [64]u8 = [0...]; // 64 binary digits
let u64tos_buf: [64]u8 = [0...];
// u64tos — convert u to a base-b numeric string. Returns a view into
// `u64tos_buf`, overwritten on the next call; copy via strings.dup to
@@ -114,7 +112,7 @@ export fn u64tos(u: u64, b: base) str = {
// i64tos_buf — independent from u64tos_buf so i64tos's own u64tos call
// (the magnitude) doesn't clobber the in-flight result. 65 = 64 digits
// plus the leading '-'. Hare's `static let buf: [65]u8` (itos.ha:18).
let i64tos_buf: [65]u8 = [0...]; // 64 binary digits plus '-'
let i64tos_buf: [65]u8 = [0...];
// i64tos — convert i to a base-b numeric string. Returns a view into
// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32.

View File

@@ -1,6 +1,4 @@
// comparetest — exercises strings.compare. A failing row aborts
// via the assert/abort builtin (task #5 @test conversion).
// Vectors mirror ref/hare/strings/compare.ha.
// Vectors mirror ref/hare/strings/compare.ha (task #5 @test conversion).
package strings_test;

View File

@@ -58,7 +58,6 @@ import os;
};
};
// ---- join -------------------------------------------------------------
// ref/hare/strings/concat.ha:64. Rows mirror Hare's @test fn join
// (0-arg, 1-arg, empty-sep, 3-arg.) plus 2-arg, all-empties, long sep,
// multibyte, empty-mid (delim still inserted around the empty slot).
@@ -111,4 +110,3 @@ import os;
i += 1;
};
};

View File

@@ -1,6 +1,4 @@
// containstest — exercises strings.contains. A failing row aborts
// via the assert/abort builtin (task #5 @test conversion).
// Vectors mirror ref/hare/strings/contains.ha.
// Vectors mirror ref/hare/strings/contains.ha (task #5 @test conversion).
package strings_test;

View File

@@ -27,7 +27,6 @@ import os;
defer os.free(m.ptr: *void, m.len: u64);
};
// ---- dupall -----------------------------------------------------------
// ref/hare/strings/dup.ha:55 (#6). Element reads go through
// `&toks.ptr[i]: *str`
// per the splitn cases (16B element copy gap, cgen.c:6515).
@@ -109,4 +108,3 @@ import os;
case nomem => { abort(); };
};
};

View File

@@ -131,7 +131,6 @@ import encoding.utf8;
};
};
// ---- prev / riter / iterstr / slice / position -----------------------
// ref/hare/strings/iter.ha:84-127. The Hare @test fn iter body uses
// `s = riter(...)` mid-test to swap the iterator's direction; ww's
// sret-into-existing-slot path handles that fine (probed pre-port).
@@ -302,4 +301,3 @@ import encoding.utf8;
match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { abort(); }; };
assert(!(!streq(strings.iterstr(&rit), "he")));
};

View File

@@ -1,5 +1,4 @@
// strings — operations over str ({ptr,len}). Hare port; see
// ref/hare/strings/.
// Hare port; see ref/hare/strings/.
//
// Documented divergences from Hare:
//
@@ -30,8 +29,8 @@ import os;
import rt;
import types;
// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29.
// `cap` equals `len`; the slice does not own a separate allocation.
// ref/hare/strings/utf8.ha:29. `cap` equals `len`; the slice does
// not own a separate allocation.
export fn toutf8(s: str) []u8 = {
let r: []u8;
r.ptr = s.ptr;
@@ -40,8 +39,8 @@ export fn toutf8(s: str) []u8 = {
return r;
};
// frombytes — borrowed str view of `in`. Pure reinterpret per
// CLAUDE.md rule 9 carve-out; ref/hare/strings/utf8.ha:10.
// Pure reinterpret per CLAUDE.md rule 9 carve-out;
// ref/hare/strings/utf8.ha:10.
export fn frombytes(in: []u8) str = {
let r: str;
r.ptr = in.ptr;
@@ -49,9 +48,8 @@ export fn frombytes(in: []u8) str = {
return r;
};
// compare — three-way bytewise codepoint-order comparison. Return is
// a sign (neg/zero/pos), not an index, so it tracks Hare's `int`
// rather than the str-index i32 (#8). ref/hare/strings/compare.ha:12.
// Return is a sign (neg/zero/pos), not an index, so it tracks Hare's
// `int` rather than the str-index i32 (#8). ref/hare/strings/compare.ha:12.
export fn compare(a: str, b: str) int = {
let n: i32 = a.len;
if (b.len < n) { n = b.len; };
@@ -63,8 +61,8 @@ export fn compare(a: str, b: str) int = {
return (a.len: int) - (b.len: int);
};
// dup — allocate a fresh copy of `s`. Caller releases with
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7.
// Caller releases with `os.free(r.ptr, r.len: u64)`.
// ref/hare/strings/dup.ha:7.
export fn dup(s: str) str = {
let r: str;
r.ptr = nil;
@@ -77,9 +75,7 @@ export fn dup(s: str) str = {
return frombytes(buf);
};
// dupall — fresh `[]str` whose elements are independent copies of
// `s`'s elements. Caller releases via [[freeall]].
// ref/hare/strings/dup.ha:26 (#6).
// Caller releases via [[freeall]]. ref/hare/strings/dup.ha:26 (#6).
//
// Hare gates the per-element dup behind `?` and rolls back via
// `defer if (!ok) freeall(newsl)`. ww has no `defer if`; more
@@ -113,8 +109,6 @@ export fn dupall(s: []str) ([]str | nomem) = {
return newsl;
};
// freeall — release each element + the slice header. The natural
// disposer for any `[]str` of dup'd elements (e.g. shlex.split).
// ref/hare/strings/dup.ha:38.
//
// Empty elements (`{nil, 0}` from a zero-length dup) are skipped:
@@ -135,8 +129,7 @@ export fn freeall(s: []str) void = {
};
};
// concat — fresh allocation containing each element of `strs` in
// order. Caller releases with `os.free(r.ptr, r.len: u64)`.
// Caller releases with `os.free(r.ptr, r.len: u64)`.
// ref/hare/strings/concat.ha:5. Hare's `nomem` return is dropped:
// `os.alloc` aborts on OOM.
export fn concat(strs: str...) str = {
@@ -163,8 +156,7 @@ export fn concat(strs: str...) str = {
return frombytes(buf);
};
// join — fresh allocation with `delim` placed between each element of
// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`.
// Caller releases with `os.free(r.ptr, r.len: u64)`.
// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped:
// `os.alloc` aborts on OOM.
export fn join(delim: str, strs: str...) str = {
@@ -203,9 +195,8 @@ export fn join(delim: str, strs: str...) str = {
return frombytes(buf);
};
// utf8bytelenbounded — walk `it` forward `end` runes and return the
// resulting byte offset. ref/hare/strings/sub.ha:10. Aborts on
// short input per Hare's contract for the rune-wise [[sub]].
// ref/hare/strings/sub.ha:10. Aborts on short input per Hare's
// contract for the rune-wise [[sub]].
fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
let i: i32 = 0;
for (i < end) {
@@ -218,10 +209,11 @@ fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
return it.offs;
};
// sub — borrowed substring [start, end) where start/end are rune
// indices. ref/hare/strings/sub.ha:30. Hare's 2-arg `sub(s, start)`
// Borrowed substring; start/end are rune indices; byte-indexed
// counterpart [[bytesub]].
// ref/hare/strings/sub.ha:30. Hare's 2-arg `sub(s, start)`
// defaulting end=END is omitted: ww has no default-parameter syntax
// (filed as #37). Byte-indexed counterpart: [[bytesub]].
// (filed as #37).
export fn sub(s: str, start: i32, end: i32) str = {
assert(start <= end, "strings.sub: start is higher than end");
let it: iterator = iter(s);
@@ -233,8 +225,9 @@ export fn sub(s: str, start: i32, end: i32) str = {
return r;
};
// bytesub — borrowed substring [start, end) where start/end are byte
// offsets. ref/hare/strings/sub.ha:59 (#7). Returns `utf8.invalid` if
// Borrowed substring; start/end are byte offsets, unlike the
// rune-wise [[sub]].
// ref/hare/strings/sub.ha:59 (#7). Returns `utf8.invalid` if
// either endpoint lands on a continuation byte (would split a
// codepoint); the equivalent Hare predicate is `s[i] & 0xc0 == 0x80`
// at ref/hare/strings/sub.ha:72-73.
@@ -253,9 +246,8 @@ export fn bytesub(s: str, start: i32, end: i32) (str | utf8.invalid) = {
return r;
};
// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes)
// and return the borrowed slice trimmed to the encoded length. Hare
// inlines the same shape at ref/hare/strings/index.ha:132.
// `scratch` must hold 4 bytes; the return borrows it. Hare inlines
// the same shape at ref/hare/strings/index.ha:132.
fn runebytes(scratch: []u8, r: rune) []u8 = {
let n: i32 = utf8.encoderune(scratch, r);
let s: []u8;
@@ -265,7 +257,6 @@ fn runebytes(scratch: []u8, r: rune) []u8 = {
return s;
};
// hasprefix — true iff `in` begins with `prefix`.
// ref/hare/strings/suffix.ha:8.
export fn hasprefix(in: str, prefix: (str | rune)) bool = {
let scratch: [4]u8;
@@ -276,7 +267,6 @@ export fn hasprefix(in: str, prefix: (str | rune)) bool = {
return bytes.hasprefix(toutf8(in), p);
};
// hassuffix — true iff `in` ends with `suff`.
// ref/hare/strings/suffix.ha:26.
export fn hassuffix(in: str, suff: (str | rune)) bool = {
let scratch: [4]u8;
@@ -287,8 +277,7 @@ export fn hassuffix(in: str, suff: (str | rune)) bool = {
return bytes.hassuffix(toutf8(in), s);
};
// byteindex — byte-wise offset of `needle` in `haystack`, or void if
// absent. ref/hare/strings/index.ha:127. Rune arm encodes via
// ref/hare/strings/index.ha:127. Rune arm encodes via
// utf8.encoderune (Hare passes the encoded slice straight to
// bytes::index).
export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
@@ -300,7 +289,6 @@ export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
return bytes.index(toutf8(haystack), n);
};
// rbyteindex — byte-wise offset of the last `needle` in `haystack`.
// ref/hare/strings/index.ha:138.
export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
let scratch: [4]u8;
@@ -311,15 +299,12 @@ export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
return bytes.rindex(toutf8(haystack), n);
};
// indexstring — str-arm of [[index]]. Dual-rune-iterator walk: at each
// candidate rune index `i`, compare `haystack` from that position
// against `needle` rune-by-rune until needle is exhausted (match) or
// a mismatch / haystack-exhaustion breaks the inner loop. Mirrors
// ref/hare/strings/index.ha:59 (#10). Hare copies `rest_iter = s_iter`
// directly via struct assignment; ww re-seats `rest_iter` field-wise
// because the let-init struct-copy form diverges between cstage and
// wwstage on this iterator type (993_ww_ww + 995_self_rebuild fail,
// filed as #41) and rule #10 (CLAUDE.md) forbids stage asymmetry.
// Mirrors ref/hare/strings/index.ha:59 (#10). Hare copies
// `rest_iter = s_iter` directly via struct assignment; ww re-seats
// `rest_iter` field-wise because the let-init struct-copy form
// diverges between cstage and wwstage on this iterator type
// (993_ww_ww + 995_self_rebuild fail, filed as #41) and rule #10
// (CLAUDE.md) forbids stage asymmetry.
fn indexstring(haystack: str, needle: str) (i32 | void) = {
let s_iter: iterator = iter(haystack);
let i: i32 = 0;
@@ -356,11 +341,10 @@ fn indexstring(haystack: str, needle: str) (i32 | void) = {
return;
};
// index — rune-wise offset of `needle`'s first occurrence in
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
// str-arm delegates to [[indexstring]] (dual-iterator rune-by-rune
// walk per Hare's `index_string`, #10); the rune-arm mirrors Hare's
// `index_rune` (ref/hare/strings/index.ha:31).
// Rune-wise offset, not byte-wise ([[byteindex]]).
// ref/hare/strings/index.ha:10. The str-arm delegates to
// [[indexstring]] (per Hare's `index_string`, #10); the rune-arm
// mirrors Hare's `index_rune` (ref/hare/strings/index.ha:31).
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
match (needle) {
case let s: str => return indexstring(haystack, s);
@@ -381,8 +365,7 @@ export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
return;
};
// rindex — rune-wise offset of `needle`'s last occurrence in
// `haystack`, or void if absent. ref/hare/strings/index.ha:22. The
// Rune-wise offset. ref/hare/strings/index.ha:22. The
// str-arm reuses `rbyteindex`; the rune-arm walks forward tracking
// the most recent matching rune index (Hare's `rindex_rune` with
// `riter` returns a byte-offset value for multibyte strings, which
@@ -426,7 +409,6 @@ export fn rindex(haystack: str, needle: (str | rune)) (i32 | void) = {
return;
};
// contains — true iff any of `needles` occurs in `haystack`.
// ref/hare/strings/contains.ha:9.
export fn contains(haystack: str, needles: (str | rune)...) bool = {
let i: i32 = 0;
@@ -450,9 +432,7 @@ export fn contains(haystack: str, needles: (str | rune)...) bool = {
return false;
};
// trimprefix — `s` with `prefix` stripped from the front, or `s`
// unchanged if it doesn't start with `prefix`. Borrowed view.
// ref/hare/strings/trim.ha:60.
// ref/hare/strings/trim.ha:60. Borrowed view.
export fn trimprefix(input: str, prefix: str) str = {
if (!hasprefix(input, prefix)) { return input; };
let r: str;
@@ -461,7 +441,7 @@ export fn trimprefix(input: str, prefix: str) str = {
return r;
};
// trimsuffix — symmetric. ref/hare/strings/trim.ha:69.
// ref/hare/strings/trim.ha:69.
export fn trimsuffix(input: str, suffix: str) str = {
if (!hassuffix(input, suffix)) { return input; };
let r: str;
@@ -470,12 +450,11 @@ export fn trimsuffix(input: str, suffix: str) str = {
return r;
};
// whitespace — ASCII whitespace set used by the 0-arg ltrim/rtrim/trim
// branches (#9). ref/hare/strings/trim.ha:6.
// ASCII set for the 0-arg ltrim/rtrim/trim branches (#9).
// ref/hare/strings/trim.ha:6.
let whitespace: [4]u8 = [0x20u8, 0x0Au8, 0x09u8, 0x0Du8];
// ltrim — strip leading runes that occur in `trim`. Borrowed view.
// 0-arg strips ASCII whitespace via [[bytes.ltrim]] (#9).
// Borrowed view. 0-arg strips ASCII whitespace via [[bytes.ltrim]] (#9).
// ref/hare/strings/trim.ha:11. The spread expression is inlined
// because `let ws: []u8 = whitespace[0:4]` produces a slice whose
// ptr doesn't track the module-level array storage (filed as #40);
@@ -509,9 +488,8 @@ export fn ltrim(input: str, trim: rune...) str = {
return iterstr(&it);
};
// rtrim — strip trailing runes that occur in `trim`. Borrowed view.
// 0-arg strips ASCII whitespace via [[bytes.rtrim]] (#9). Spread is
// inlined to dodge #40 — see [[ltrim]].
// Borrowed view; inlined to dodge #40 — see [[ltrim]].
// ref/hare/strings/trim.ha:32.
export fn rtrim(input: str, trim: rune...) str = {
if (trim.len == 0) {
@@ -541,23 +519,22 @@ export fn rtrim(input: str, trim: rune...) str = {
return iterstr(&it);
};
// trim — strip from both ends. ref/hare/strings/trim.ha:54.
// ref/hare/strings/trim.ha:54.
export fn trim(input: str, trim: rune...) str = {
return ltrim(rtrim(input, trim...), trim...);
};
// iterator — UTF-8 rune cursor over a `str`. Layout flattens Hare's
// anonymous-embedded `utf8::decoder` (ref/hare/strings/iter.ha:6-9) to
// explicit fields. `reverse` selects walk direction: forward iterators
// (`iter`) advance through utf8.next; reverse iterators (`riter`) advance
// through utf8.prev. May be copied to save state.
// Layout flattens Hare's anonymous-embedded `utf8::decoder`
// (ref/hare/strings/iter.ha:6-9) to explicit fields. `reverse` selects
// walk direction: forward iterators (`iter`) advance through utf8.next;
// reverse iterators (`riter`) advance through utf8.prev. May be copied
// to save state.
export type iterator = struct {
offs: i32,
src: []u8,
reverse: bool,
};
// iter — initialize a forward iterator at the start of `src`.
// ref/hare/strings/iter.ha:24.
export fn iter(src: str) iterator = {
let r: iterator;
@@ -567,8 +544,6 @@ export fn iter(src: str) iterator = {
return r;
};
// riter — initialize a reverse iterator at the end of `src`. `next`
// on a reverse iterator walks back through the string.
// ref/hare/strings/iter.ha:32.
export fn riter(src: str) iterator = {
let r: iterator;
@@ -578,8 +553,7 @@ export fn riter(src: str) iterator = {
return r;
};
// move — private dispatch shared by next/prev. `forward` selects
// utf8.next vs utf8.prev. Aborts on more/invalid per Hare's
// Aborts on more/invalid per Hare's
// ref/hare/strings/iter.ha:51-58 ("Invalid UTF-8 string (this should
// not happen)"). Hare picks the utf8 function via a fn-pointer; ww
// branches on `forward` at each call site instead.
@@ -607,22 +581,17 @@ fn move(forward: bool, it: *iterator) (rune | utf8.done) = {
};
};
// next — advance the iterator one rune. Forward iterators step
// through utf8.next; reverse iterators (riter) step backward through
// utf8.prev. Returns utf8.done at end-of-walk. ref/hare/strings/iter.ha:45.
// ref/hare/strings/iter.ha:45.
export fn next(it: *iterator) (rune | utf8.done) = {
return move(!it.reverse, it);
};
// prev — step back one rune. Dual to next: on a forward iterator
// this walks utf8.prev; on a reverse iterator (riter) it walks
// utf8.next. ref/hare/strings/iter.ha:49.
// ref/hare/strings/iter.ha:49.
export fn prev(it: *iterator) (rune | utf8.done) = {
return move(it.reverse, it);
};
// iterstr — borrowed view of the bytes remaining in the iterator's
// walk direction. Forward iter: bytes from offs to end; reverse iter:
// Borrowed view. Forward iter: bytes from offs to end; reverse iter:
// bytes from start to offs. ref/hare/strings/iter.ha:63.
export fn iterstr(it: *iterator) str = {
let r: []u8;
@@ -634,7 +603,6 @@ export fn iterstr(it: *iterator) str = {
return frombytes(r);
};
// slice — borrowed substring between two iterator positions.
// ref/hare/strings/iter.ha:75. Hare passes `*iterator` directly where
// `*utf8::decoder` is expected via anonymous-embed coercion; ww has
// no anonymous embed, so we reconstruct a local utf8.decoder for each
@@ -649,22 +617,19 @@ export fn slice(begin: *iterator, end: *iterator) str = {
return frombytes(utf8.slice(&b, &e));
};
// position — byte-wise offset of the iterator in its source.
// ref/hare/strings/iter.ha:82.
export fn position(it: *iterator) i32 = {
return it.offs;
};
// tokenizer — re-export of bytes.tokenizer. ref/hare/strings/tokenize.ha:7.
// ref/hare/strings/tokenize.ha:7.
// First cross-module type alias in tree; needs #22's transitive
// alias-chain unwrap (cstage type_chase_named + wwstage
// structlookupchain) to walk struct fields through the chain.
export type tokenizer = bytes.tokenizer;
// tokenize — yield substrings of `s` split on any byte in `delim`.
// Leading / trailing / adjacent delims yield empty tokens. `s` and
// `delim` are borrowed; caller keeps them live for the tokenizer's
// lifetime. ref/hare/strings/tokenize.ha:32. ASCII-only delim
// `s` and `delim` are borrowed; caller keeps them live for the
// tokenizer's lifetime. ref/hare/strings/tokenize.ha:32. ASCII-only delim
// asserted per Hare lines 35-37: a multibyte rune in delim would
// split on a single continuation byte and yield invalid UTF-8.
export fn tokenize(s: str, delim: str) tokenizer = {
@@ -678,8 +643,6 @@ export fn tokenize(s: str, delim: str) tokenizer = {
return bytes.tokenize(toutf8(s), d...);
};
// rtokenize — reverse-direction counterpart to [[tokenize]]. First
// nexttoken yields the last token, last yields the first.
// ref/hare/strings/tokenize.ha:44.
export fn rtokenize(s: str, delim: str) tokenizer = {
let d: []u8 = toutf8(delim);
@@ -692,7 +655,6 @@ export fn rtokenize(s: str, delim: str) tokenizer = {
return bytes.rtokenize(toutf8(s), d...);
};
// nexttoken — current token, advancing the cursor.
// ref/hare/strings/tokenize.ha:62.
export fn nexttoken(s: *tokenizer) (str | bytes.done) = {
let b: *bytes.tokenizer = s: *bytes.tokenizer;
@@ -702,7 +664,6 @@ export fn nexttoken(s: *tokenizer) (str | bytes.done) = {
};
};
// peektoken — current token without advancing.
// ref/hare/strings/tokenize.ha:71.
export fn peektoken(s: *tokenizer) (str | bytes.done) = {
let b: *bytes.tokenizer = s: *bytes.tokenizer;
@@ -712,34 +673,29 @@ export fn peektoken(s: *tokenizer) (str | bytes.done) = {
};
};
// remainingtokens — unconsumed portion of the input ahead of the
// cursor. ref/hare/strings/tokenize.ha:79.
// ref/hare/strings/tokenize.ha:79.
export fn remainingtokens(s: *tokenizer) str = {
let b: *bytes.tokenizer = s: *bytes.tokenizer;
return frombytes(bytes.remainingtokens(b));
};
// cut — split `in` along the first instance of `delim`, returning the
// portions before and after it. When `delim` is absent the whole input
// is the first half and the second is empty. Both halves are borrowed
// from `in`; caller ensures `delim` is non-empty.
// ref/hare/strings/tokenize.ha:288.
// When `delim` is absent the whole input is the first half and the
// second is empty. Both halves are borrowed from `in`; caller
// ensures `delim` is non-empty. ref/hare/strings/tokenize.ha:288.
export fn cut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// rcut — like [[cut]] but split along the LAST instance of `delim`.
// ref/hare/strings/tokenize.ha:302.
export fn rcut(in: str, delim: str) (str, str) = {
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
return (frombytes(a), frombytes(b));
};
// splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings
// within the result are borrowed from `in`.
// The trailing slot (when more than `n - 1` tokens exist) holds the
// unconsumed remainder. Strings within the result are borrowed
// from `in`.
//
// The caller frees the returned slice via
// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`.
@@ -773,9 +729,8 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
return toks;
};
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
// collected from the end of `in`. The trailing slot holds the
// unconsumed prefix (everything before the n-th-from-last delim hit).
// The trailing slot holds the unconsumed prefix (everything before
// the n-th-from-last delim hit).
//
// When the input has fewer than n tokens, the `done` short-circuit
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
@@ -826,8 +781,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
return toks;
};
// split — full split of `in` on `delim` (no token cap). Mirrors
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
// Mirrors `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
// because the index type is i32 (lib/CLAUDE.md).
//
// ref/hare/strings/tokenize.ha:242.
@@ -835,8 +789,7 @@ export fn split(in: str, delim: str) []str = {
return splitn(in, delim, types.I32_MAX);
};
// lpad — left-pad `s` with `p` rune until the result reaches `maxlen`
// bytes. Length comparison is BYTES, mirroring Hare's `len(s) >= maxlen`
// Length comparison is BYTES, mirroring Hare's `len(s) >= maxlen`
// at ref/hare/strings/pad.ha:9. A multibyte `p` whose encoded width
// doesn't divide `maxlen - s.len` evenly leaves a trailing pad byte
// pair sliced mid-codepoint at byte `maxlen-1`, exactly as Hare's
@@ -872,9 +825,9 @@ export fn lpad(s: str, p: rune, maxlen: i32) str = {
return frombytes(buf);
};
// replace — fresh allocation of `s` with every non-overlapping
// occurrence of `needle` replaced by `target`. Caller releases with
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/replace.ha:8 (#4).
// Replaces every non-overlapping occurrence of `needle`. Caller
// releases with `os.free(r.ptr, r.len: u64)`.
// ref/hare/strings/replace.ha:8 (#4).
//
// Hare delegates to [[multireplace]] with a single pair; ww has no
// `(str, str)` variadic shape today (#39), so this is a standalone
@@ -928,8 +881,7 @@ export fn replace(s: str, needle: str, target: str) (str | nomem) = {
return frombytes(res);
};
// rpad — right-pad `s` with `p` rune until the result reaches `maxlen`
// bytes. Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
// Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
export fn rpad(s: str, p: rune, maxlen: i32) str = {
if (s.len >= maxlen) { return dup(s); };
let scratch: [4]u8;

View File

@@ -1,6 +1,4 @@
// suffixtest — exercises strings.hasprefix/hassuffix. A failing row
// aborts via the assert/abort builtin (task #5 @test conversion).
// Vectors mirror ref/hare/strings/suffix.ha.
// Vectors mirror ref/hare/strings/suffix.ha (task #5 @test conversion).
package strings_test;

View File

@@ -162,7 +162,6 @@ fn expect_str_done(t: *strings.tokenizer) void = {
assert(!(!streq(strings.remainingtokens(&t2), "a b c")));
};
// ---- splitn / rsplitn / split ----------------------------------------
// ref/hare/strings/tokenize.ha:245 @test fn split. Hare's vectors
// mirrored here directly; element reads go through `&toks.ptr[i]: *str`
// rather than `toks[i]` so the 16B str element copy stays out of the
@@ -290,7 +289,6 @@ fn expect_str(toks: []str, i: i32, want: str) void = {
os.free(t3.ptr: *void, (t3.cap: u64) * size(str): u64);
};
// ---- cut / rcut -------------------------------------------------------
// ref/hare/strings/tokenize.ha:316 (@test fn cut). str wrappers over
// bytes.cut/rcut. The `let (a, b) = cut(...)` destructure drives the
// over-cap tuple-return (sret) path end-to-end (a 2nd #10 witness).

View File

@@ -26,7 +26,6 @@ import strings;
assert(!(!streq(strings.trimsuffix("hello", "hello"), "")));
};
// ---- ltrim / rtrim / trim ---------------------------------------------
// ref/hare/strings/trim.ha:75-97. 0-arg rows (#9) pin the ASCII
// whitespace set (' ', '\t', '\n', '\r' — ref/hare/strings/trim.ha:6).
@@ -167,4 +166,3 @@ import strings;
i += 1;
};
};

View File

@@ -113,9 +113,8 @@ fn nextrand() u64 = {
// exposes getenv; documented in the file header.
fn gettmpdir() str = { return "/tmp"; };
// puts — copy `s` into pathbuf starting at `off`. Caps writes
// against pathbuf's capacity so a long caller-supplied dir can't
// run off the end. Returns the new offset.
// Caps writes against pathbuf's capacity so a long caller-supplied
// dir can't run off the end.
fn puts(off: i32, s: str) i32 = {
let i: i32 = 0;
for (i < s.len) {
@@ -126,7 +125,6 @@ fn puts(off: i32, s: str) i32 = {
return off + i;
};
// puthex — 16 lowercase hex digits of `v` into pathbuf[off..off+16].
fn puthex(off: i32, v: u64) i32 = {
let hex: str = "0123456789abcdef";
let i: i32 = 0;

View File

@@ -1,6 +1,3 @@
// temptest — exercises lib/temp. Run with
// `out/bin/ww build lib/temp/temptest.ww && ./temptest`.
//
// 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
@@ -28,8 +25,6 @@ fn streq(a: str, b: str) bool = {
return true;
};
// ---- namedroundtrip: write + read-back across payload sizes ------------
@test fn namedroundtrip() void = {
// (payload size, fill byte). Row 0 covers the zero-byte edge.
let sz: [4]i32;
@@ -56,7 +51,6 @@ fn streq(a: str, b: str) bool = {
assert(!(!streq(strslice(p, 0, 10), "/tmp/temp.")));
assert(!(p.ptr[p.len] != 0u8));
// Build fill payload, write it, lseek to 0, read it back.
let wbuf: [64]u8;
let k: i32 = 0;
for (k < sz[i]) { wbuf[k] = fill[i]; k += 1; };
@@ -82,13 +76,11 @@ fn streq(a: str, b: str) bool = {
k2 += 1;
};
// File exists pre-cleanup.
assert(!(os.access(p, 0i32) != 0));
assert(!(os.close(fd) != 0));
assert(!(os.remove(p) != 0));
// Cleanup landed.
assert(!(os.access(p, 0i32) == 0));
i += 1;
@@ -104,8 +96,6 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
return r;
};
// ---- namedoverwrite: static buffer is reused across calls --------------
//
// Hare docs: "The name is statically allocated, and will be
// overwritten on subsequent calls." Match that contract — the second
// named() call lands in the same buffer, so p1.ptr == p2.ptr.
@@ -141,7 +131,6 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
assert(!(p1.ptr != p2.ptr));
assert(!(streq(strslice(p2, 0, p2.len), psnap)));
// Both fds are distinct.
assert(!(fd1 == fd2));
assert(!(os.close(fd2) != 0));
@@ -160,8 +149,6 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
// Coverage for the underlying open+create+EXCL path lives in
// [[namedroundtrip]] / [[namedoverwrite]].
// ---- dirlifecycle: empty dir, then dir + one child file ----------------
@test fn dirlifecycle() void = {
// (child count). Row 0: empty dir. Row 1: dir + one file.
let childn: [2]i32;
@@ -175,7 +162,6 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
assert(!(!streq(strslice(d, 0, 5), "/tmp/")));
assert(!(d.ptr[d.len] != 0u8));
// Dir exists.
assert(!(os.access(d, 0i32) != 0));
// Snapshot the dir path into a local NUL-terminated buffer:
@@ -192,7 +178,6 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
dview.ptr = &dsnap[0]; dview.len = dlen;
if (childn[i] > 0) {
// Build "<d>/x\0" in a local buffer.
let cbuf: [144]u8;
let off: i32 = 0;
let j: i32 = 0;
@@ -217,15 +202,12 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
// touched between dir() and here).
assert(!(os.rmdir(dview) != 0));
// Cleanup landed.
assert(!(os.access(dview, 0i32) == 0));
i += 1;
};
};
// ---- diruniqueness: two dir() calls produce different paths ------------
@test fn diruniqueness() void = {
let d1: str = temp.dir();
let snap: [128]u8;

View File

@@ -78,8 +78,8 @@ export fn sleep(d: duration, c: clock) void = {
};
};
// ref/hare/time/arithm.ha:9. Adds duration to instant. The
// negative-duration branch normalises nsec into [0, second).
// ref/hare/time/arithm.ha:9. The negative-duration branch normalises
// nsec into [0, second).
export fn add(i: instant, x: duration) instant = {
let r: instant;
let xi: i64 = x: i64;
@@ -100,8 +100,7 @@ export fn add(i: instant, x: duration) instant = {
return r;
};
// ref/hare/time/arithm.ha:26. Returns duration from a to b.
// Sign convention: b - a.
// ref/hare/time/arithm.ha:26. Sign convention: b - a.
export fn diff(a: instant, b: instant) duration = {
let sec: i64 = second: i64;
let v: i64 = ((b.sec - a.sec) * sec) + (b.nsec - a.nsec);

View File

@@ -1,7 +1,4 @@
// lib/ww/syntax/ast.ww — port of cmd/wcc/ast.c (Node defs + printer).
//
// Status: AST printer is fully ported. Constructor `newnode` is here.
// The parser (parse.ww) is currently minimal — see its file header.
// Port of cmd/wcc/ast.c (Node defs + printer).
//
// Calling-convention shim: same as tok/lex — `node` is too big to pass
// by value (8 *node pointers + 2 strs + a few ints), so callers always
@@ -12,14 +9,10 @@ package syntax;
import os;
import strconv;
// ---- Nkind ------------------------------------------------------------
//
// Mirror of cmd/wcc/ww.h Nkind. Values must stay numerically equal
// to the C side (rule-6 data-shape mirror).
// Mirror of the C `Nkind` enum in cmd/wcc/ww.h. Numeric values are
// explicit and must stay in sync with the C side. Tail-appended
// entries (TYPETEST onward) preserve every prior N_* value.
// Mirror of the C `Nkind` enum in cmd/wcc/ww.h (rule-6 data-shape
// mirror). Numeric values are explicit and must stay in sync with the
// C side. Tail-appended entries (TYPETEST onward) preserve every prior
// N_* value.
export type nkind = enum i32 {
N_NONE = 0,
@@ -112,8 +105,6 @@ export type nkind = enum i32 {
N_LAST = 68,
};
// ---- Node -------------------------------------------------------------
export type node = struct {
kind: nkind,
file: str,
@@ -150,8 +141,6 @@ export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
return n;
};
// ---- printer ----------------------------------------------------------
export fn nkname(k: nkind) str = {
switch (k) {
case nkind.N_NONE: return "none";

View File

@@ -1,12 +1,9 @@
// asttest — functional-equivalence pin for [[nkname]] after the
// if-ladder → switch fold (struct fold S2). Run with
// `ww run -I lib/ww lib/ww/syntax/asttest.ww`.
// Functional-equivalence pin for [[nkname]] after the
// if-ladder → switch fold (struct fold S2).
//
// nkname is checked against every nkind value (the full ladder the
// switch replaced) plus the out-of-band fallback ("?"). A failing row
// aborts via the assert/abort builtin (task #5 @test conversion).
// `package main` + bare `import ast` mirrors wwdump (the external
// astprint consumer).
package syntax_test;

View File

@@ -1,4 +1,4 @@
// lib/ww/syntax/decl.ww — declaration parsing, split out of parse.ww.
// Declaration-parsing half of the cmd/wcc/parse.c port.
package syntax;
@@ -14,7 +14,7 @@ fn parseuse(p: *parser) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p); // past `use`
advance(p);
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
n.nmod = p.curmod;
// M1 #22: accumulate the full dotted import path (n.usepath) for the
@@ -24,7 +24,7 @@ fn parseuse(p: *parser) *node = {
expectident(p, &leaf);
let path: str = leaf;
for (p.curkind == tkind.TK_DOT) {
advance(p); // past `.`
advance(p);
expectident(p, &leaf);
path = strings.concat(path, ".", leaf);
};
@@ -38,7 +38,7 @@ fn parsedef(p: *parser, exported: i32) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p); // past `def`
advance(p);
let n: *node = newnode(nkind.N_DEF, pf, pl, pc);
n.nmod = p.curmod;
let id: str;
@@ -160,7 +160,7 @@ fn parsefn(p: *parser, exported: i32, attrs: *node) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p); // past `fn`
advance(p);
let n: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
n.nmod = p.curmod;
let id: str;
@@ -190,7 +190,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
let pf: str = p.curfile;
let pl: i32 = p.curline;
let pc: i32 = p.curcol;
advance(p); // past `type`
advance(p);
let n: *node = newnode(nkind.N_TYPEDECL, pf, pl, pc);
n.nmod = p.curmod;
let id: str;
@@ -202,4 +202,3 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
n.exported = exported;
return n;
};

View File

@@ -1,11 +1,10 @@
// lib/ww/syntax/expr.ww — expression parsing, split out of parse.ww.
// Expression-parsing half of the cmd/wcc/parse.c port.
package syntax;
import os;
// streqlocal — str-to-str compare. Inlined here to avoid a cross-
// module `use sym;` for one call site.
// Inlined to avoid a cross-module `use sym;` for one call site.
fn streqlocal(a: str, b: str) bool = {
if (a.len != b.len) { return false; };
let i: i32 = 0;
@@ -113,7 +112,6 @@ fn parseprimary(p: *parser) *node = {
if (p.curkind == tkind.TK_LPAREN) {
advance(p);
let e: *node = parseexpr(p);
// Tuple literal: (a, b, ...)
if (accepttok(p, tkind.TK_COMMA)) {
let t: *node = newnode(nkind.N_TUPLE, pf, pl, pc);
t.list = e;
@@ -194,7 +192,7 @@ fn parseprimary(p: *parser) *node = {
let cf: str = p.curfile;
let cl: i32 = p.curline;
let cc: i32 = p.curcol;
advance(p); // past `case`
advance(p);
let mc: *node = newnode(nkind.N_MCASE, cf, cl, cc);
if (p.curkind == tkind.TK_LET) {
advance(p);
@@ -545,4 +543,3 @@ fn parseexpr(p: *parser) *node = {
};
return e;
};

View File

@@ -1,4 +1,4 @@
// lib/ww/syntax/lex.ww — port of cmd/wcc/lex.c.
// Port of cmd/wcc/lex.c.
//
// The DFA, the helpers, and the order of decisions all mirror the C
// version exactly; any divergence surfaces as a cs/ww byte split in
@@ -18,9 +18,8 @@ import strings;
import strconv;
import encoding.utf8;
// isidstart / isidpart — identifier classification. Lexer-local
// because the "alpha or '_' / alnum or '_'" set isn't part of Hare's
// ascii::; ascii::isalpha + the '_' check live here instead.
// Lexer-local because the "alpha or '_' / alnum or '_'" set isn't part
// of Hare's ascii::; ascii::isalpha + the '_' check live here instead.
fn isidstart(c: rune) bool = {
if (ascii.isalpha(c)) { return true; };
if (c == '_') { return true; };
@@ -33,8 +32,6 @@ fn isidpart(c: rune) bool = {
return false;
};
// hexval — value of `c` as a hex digit (0..15) or void if not a hex
// digit. Used by string-literal `\xHH` escapes.
fn hexval(c: rune) (i32 | void) = {
if (ascii.isdigit(c)) { return (c - '0'): i32; };
if (c >= 'A') {
@@ -81,7 +78,6 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
l.modresetpathset = 0;
};
// srcb — byte at offset; helper that lifts the cast out of indexing.
fn srcb(l: *lex, off: u64) i32 = {
let i: i32 = off: i32;
let b: u8 = l.src[i];
@@ -139,7 +135,7 @@ fn skipws(l: *lex) bool = {
if (c == '/') {
let c2: i32 = lpeek(l, 1u64);
if (c2 == '/') {
lget(l); lget(l); // consume '//'
lget(l); lget(l);
// #16 opt-B: recognize the driver's curmod-reset
// boundary directive `//ww:module-reset` (whole
// line) and flag it; lexnext emits TK_MODRESET.
@@ -371,7 +367,6 @@ fn escape(l: *lex, out: *i32) bool = {
return false;
};
// scandecimalrun — consume a run of decimal digits and underscores.
fn scandecimalrun(l: *lex) void = {
for (true) {
let c: i32 = lpeek(l, 0u64);
@@ -415,7 +410,6 @@ fn scanoctrun(l: *lex) void = {
};
};
// scanexp — consume the [eE][+-]?[0-9]+ tail of a float, if present.
fn scanexp(l: *lex) void = {
let e: i32 = lpeek(l, 0u64);
if (e != 'e') { if (e != 'E') { return; }; };
@@ -752,8 +746,6 @@ fn emitsimple(start: *pos, k: tkind, out: *tok) void = {
out.col = start.col;
};
// setposfrom — copy file/line/col from a *pos into a tok. Used by
// the err-token path where we already have a pos.
fn setposfrom(out: *tok, p: *pos) void = {
out.file = p.file;
out.line = p.line;

View File

@@ -1,9 +1,4 @@
// lib/ww/syntax/parse.ww — port of cmd/wcc/parse.c (entry + plumbing).
//
// Split into Hare-style submodule: parse.ww (here) holds the parser
// struct, lexer plumbing, parsetype, parsefile (entry). Expression,
// statement, and declaration parsers live in expr.ww, stmt.ww,
// decl.ww respectively — all in the same `parse` module.
// Port of cmd/wcc/parse.c (entry + plumbing).
//
// Calling-convention shim: w6c can't yet pass a sub-struct field
// (e.g. p.cur.line where p.cur is a `tok` of size 76). The parser
@@ -96,7 +91,6 @@ fn expecttok(p: *parser, k: tkind, what: str) bool = {
return false;
};
// expectident — consume the current tkind.TK_IDENT and return its text.
// Returns the empty str on error (and advances to make progress).
fn expectident(p: *parser, into: *str) bool = {
if (p.curkind != tkind.TK_IDENT) {
@@ -109,9 +103,7 @@ fn expectident(p: *parser, into: *str) bool = {
return true;
};
// expectbindname — like expectident but also accepts a bare `_`
// discard marker. On `_`, returns "" so the checker skips
// scope_define for the binding.
// On `_`, returns "" so the checker skips scope_define for the binding.
fn expectbindname(p: *parser, into: *str) bool = {
if (p.curkind == tkind.TK_UNDER) {
*into = "";
@@ -121,14 +113,7 @@ fn expectbindname(p: *parser, into: *str) bool = {
return expectident(p, into);
};
// ---- type expressions ------------------------------------------------
//
// Currently: TNAME (single ident, no dotted path yet) and TPTR (`*T`).
// Other forms (slice, array, struct, fn, chan, tuple, tagged) will
// land in subsequent commits.
// joindotted — build "head.tail" for dotted type-name path
// collapse. Mirrors aprintf in C parser; pulled local to avoid a
// Mirrors aprintf in the C parser; pulled local to avoid a
// cross-module dependency.
fn joindotted(head: str, tail: str) str = {
let n: u64 = head.len: u64 + 1u64 + tail.len: u64;
@@ -391,13 +376,6 @@ fn parsetype(p: *parser) *node = {
return newnode(nkind.N_TNAME, pf, pl, pc);
};
// ---- expressions (Pratt) ---------------------------------------------
//
// Forwards: parseexpr → parsebin → parseunary → parsepostfix(parseprimary).
// Tuple literals, match expressions, struct literals, slice [lo:hi],
// and the ?/! try operators are not yet wired — they'll arrive as the
// AST diff fixture grows to need them.
fn bprec(k: tkind) i32 = {
if (k == tkind.TK_OR) { return 1; };
if (k == tkind.TK_AND) { return 2; };

View File

@@ -1,4 +1,4 @@
// lib/ww/syntax/stmt.ww — statement parsing, split out of parse.ww.
// Statement-parsing half of the cmd/wcc/parse.c port.
package syntax;
@@ -118,7 +118,7 @@ fn parseif(p: *parser) *node = {
let pf = p.curfile;
let pl = p.curline;
let pc = p.curcol;
advance(p); // past `if`
advance(p);
expecttok(p, tkind.TK_LPAREN, "expected '(' after if");
let n = newnode(nkind.N_IF, pf, pl, pc);
n.cond = parseexpr(p);
@@ -138,7 +138,7 @@ fn parsefor(p: *parser) *node = {
let pf = p.curfile;
let pl = p.curline;
let pc = p.curcol;
advance(p); // past `for`
advance(p);
// Go's bare `for { }` — no header at all (cmd/wcc/parse.c:1023;
// Go's for has exactly three forms and this is the empty one).
@@ -172,9 +172,8 @@ fn parsefor(p: *parser) *node = {
// Range and 3-clause both lead with `let`, so we commit to consuming
// `let` then disambiguate by looking at what follows.
if (p.curkind == tkind.TK_LET) {
advance(p); // past `let`
advance(p);
// Tuple destructure: `for (let (a, b) .. expr)`.
if (p.curkind == tkind.TK_LPAREN) {
advance(p);
let names: *node = nil;
@@ -215,7 +214,7 @@ fn parsefor(p: *parser) *node = {
let lpf = p.curfile;
let lpl = p.curline;
let lpc = p.curcol;
advance(p); // consume IDENT/UNDER
advance(p);
if (p.curkind == tkind.TK_DOTDOT) {
advance(p);
@@ -277,7 +276,7 @@ fn parseswitch(p: *parser) *node = {
let pf = p.curfile;
let pl = p.curline;
let pc = p.curcol;
advance(p); // past `switch`
advance(p);
expecttok(p, tkind.TK_LPAREN, "expected '(' after switch");
let n = newnode(nkind.N_SWITCH, pf, pl, pc);
n.lhs = parseexpr(p);
@@ -289,7 +288,7 @@ fn parseswitch(p: *parser) *node = {
let cpf = p.curfile;
let cpl = p.curline;
let cpc = p.curcol;
advance(p); // past `case`
advance(p);
let cs = newnode(nkind.N_CASE, cpf, cpl, cpc);
let eh: *node = nil;
let et: *node = nil;
@@ -437,4 +436,3 @@ fn parsestmt(p: *parser) *node = {
expecttok(p, tkind.TK_SEMI, "expected ';' after expression statement");
return n;
};

View File

@@ -1,8 +1,7 @@
// lib/ww/syntax/sym.ww — port of cmd/wcc/sym.c.
// Port of cmd/wcc/sym.c.
//
// Per-scope hashtable, chained to the parent. Lookup walks up.
// Plan 9 / Hare flavoured. Duplicate definitions in the same scope
// return nil; the caller flags the error.
// Duplicate definitions in the same scope return nil; the caller
// flags the error.
package syntax;
@@ -103,11 +102,8 @@ export fn scopelookup(s: *scope, name: str) *sym = {
return nil;
};
// scopelookuptype — find an SK_TYPE entry by name, same-module preferred.
//
// Same FNV bucket + hashnext chain + parent walk as scopelookup, with
// an `skind == SK_TYPE` filter. Used to disambiguate the bare-TNAME
// vs imported-module-bareword collision: when scopelookup returns the
// Disambiguates the bare-TNAME vs imported-module-bareword
// collision: when scopelookup returns the
// SK_USE sym for a leaf that ALSO names a type (a same-name `import X;`
// SK_USE shadowing a struct X declared in another module), the resolver
// needs the type entry — the struct's mod may differ from the leaf so
@@ -151,10 +147,7 @@ export fn scopelookuptype(s: *scope, mod: str, name: str) *sym = {
return nil;
};
// scopelookupuselocal — find a same-leaf SK_USE entry within ONE scope.
//
// Same FNV bucket + hashnext chain as scopelookuplocal, with a
// `skind == SK_USE` filter and NO parent walk. The dot-lhs twin of
// The dot-lhs twin of
// scopelookuptype: when a `use mod;` and a colliding top-level
// `fn mod` / `type mod` of the same leaf coexist (random.random,
// fnmatch.fnmatch), the mod-preferring scopelookupprefer returns the
@@ -198,12 +191,9 @@ export fn scopelookupuselocal(s: *scope, name: str) *sym = {
return nil;
};
// scopelookupinmodule — module-filtered chain walk.
//
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
// a `b.mod.len > 0 && streq(b.mod, mod)` filter. When `mod` is empty
// we fall back to unfiltered scopelookup semantics, so callers that
// don't care about disambiguation get the default.
// When `mod` is empty we fall back to unfiltered scopelookup
// semantics, so callers that don't care about disambiguation get the
// default.
//
// Used by the dot-prefixed type-name lookup in selfhost/cmd/wcc/
// check.ww to pick the right same-leaf-name type when two imports
@@ -229,10 +219,7 @@ export fn scopelookupinmodule(s: *scope, mod: str, name: str) *sym = {
return nil;
};
// scopelookupprefer — bare-leaf lookup with same-module preference.
//
// Walks the same FNV bucket + hashnext chain + parent walk scopelookup
// uses. Within each scope's bucket: Pass 1 prefers entries whose
// Within each scope's bucket: Pass 1 prefers entries whose
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
// regardless of mod (same semantics as scopelookup). We only descend
// to the parent scope when the current scope has no matching entry at
@@ -276,8 +263,6 @@ export fn scopedefine(s: *scope, name: str, k: skind, t: *tinfo, decl: *node) *s
return scopedefineinmodule(s, name, empty, k, t, decl);
};
// scopedefineinmodule — bucket insert with per-mod dedup.
//
// Same insertion as scopedefine, but the duplicate-rejection key is
// (name, mod) rather than name alone. This lets two imports each
// register their own `stream` SK_TYPE in the flat scope, and lets the

View File

@@ -1,13 +1,11 @@
// symtest — behavior pin for [[newscope]]/[[scopedefine]]/[[scopelookup]]
// Behavior pin for [[newscope]]/[[scopedefine]]/[[scopelookup]]
// (hashtable scope semantics: define, same-scope duplicate reject,
// kind-preserving lookup, not-found nil). Run with
// `ww test -I lib/ww lib/ww/syntax/symtest.ww`.
// kind-preserving lookup, not-found nil).
//
// Migrated from selfhost/test/sym_link.ww (the 990_selfhost link
// probe): the toolchain-link half of that probe is owned by the
// fixture corpus' ww-stage cells, so only the scope behavior rows
// survive, as in-language rows. `package main` + bare `import syntax`
// mirrors toktest/asttest.
// survive, as in-language rows.
package syntax_test;

View File

@@ -1,12 +1,8 @@
// lib/ww/syntax/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
// Tok / Pos shapes from cmd/wcc/ww.h.
// Port of cmd/wcc/tok.c plus the Tkind / Tok / Pos shapes from cmd/wcc/ww.h.
//
// Token kind values must stay numerically equal to the C side
// (rule-6 data-shape mirror of cmd/wcc/ww.h). Reordering this list
// shifts the integers and splits the two frontends.
//
// Bottom of file: tokprint, which emits one token per line in a
// format identical to cmd/wcc/tok.c:tokprint().
package syntax;
@@ -14,7 +10,6 @@ import os;
import strconv;
import strings;
// ---- tkind ------------------------------------------------------------
// Mirror of the C `Tkind` enum in cmd/wcc/ww.h. Numeric values are
// explicit and must stay in sync with the C side.
@@ -125,8 +120,6 @@ export type tkind = enum i32 {
TK_LAST = 89,
};
// ---- Pos / Tok --------------------------------------------------------
//
// `pos` is used at error-reporting boundaries; we always pass it via
// *pos so the value never gets struct-copied (w6c can't yet copy a
// 24-byte struct).
@@ -152,8 +145,6 @@ export type tok = struct {
tsuffix: str, // typed numeric literal suffix or empty
};
// ---- keyword lookup ---------------------------------------------------
// keep alphabetised, so kwlookup is easy to read — mirrors the C twin
// cmd/wcc/tok.c:18-47. Two parallel arrays, not a [N]kwent array-of-
// struct: a str *inside* an aggregate element is the filed #18 follow-up
@@ -180,9 +171,7 @@ let kwkinds: [30]tkind = [
tkind.TK_VOID, tkind.TK_YIELD,
];
// kwlookup — returns the matching TK_* keyword kind for a byte run,
// or tkind.TK_NONE if it's an ordinary identifier. Linear scan over the
// table, matching cmd/wcc/tok.c:kwlookup (N=30, no hash).
// Linear scan over the table, matching cmd/wcc/tok.c:kwlookup (N=30, no hash).
export fn kwlookup(p: *u8, n: i32) tkind = {
let cand: str;
cand.ptr = p;
@@ -197,10 +186,7 @@ export fn kwlookup(p: *u8, n: i32) tkind = {
return tkind.TK_NONE;
};
// ---- tokname ----------------------------------------------------------
//
// Returns the canonical printable spelling for a token kind. Matches
// the C tokname()'s output exactly so wwdump output diffs cleanly.
// Matches the C tokname()'s output exactly so wwdump output diffs cleanly.
export fn tokname(k: tkind) str = {
switch (k) {
@@ -306,8 +292,6 @@ export fn tokname(k: tkind) str = {
return "<?>";
};
// ---- writer for tokprint ----------------------------------------------
//
// fputq mirrors cmd/wcc/tok.c:fputq — quote the string with C-style
// escapes for \, ", \n, \t, \r and \xNN for other non-printables.
@@ -358,8 +342,8 @@ fn fputq(fd: i32, p: *u8, n: i32) void = {
fputcbyte(fd, '"');
};
// tokprint — write one token line to fd. Format must match
// cmd/wcc/tok.c:tokprint() byte-for-byte: that's the diff anchor.
// Format must match cmd/wcc/tok.c:tokprint() byte-for-byte: that's
// the diff anchor.
// "<file>:<line>:<col> <kindname>[ <value>]\n"
//
// Takes `t` by pointer because w6c can't yet pass a >16-byte struct

View File

@@ -1,7 +1,6 @@
// toktest — functional-equivalence pin for [[tokname]], [[kwlookup]]
// Functional-equivalence pin for [[tokname]], [[kwlookup]]
// (struct fold S1) and [[tokprint]]/fputq (struct fold S9, the
// if-ladder → switch folds in tok.ww).
// Run with `ww run -I lib/ww lib/ww/syntax/toktest.ww`.
//
// tokname is checked against every tkind value (the full ladder the
// switch replaced, plus the unknown-kind fallback); kwlookup is
@@ -16,8 +15,6 @@
// A failing row aborts via the assert/abort builtin (task #5 @test
// conversion); per-row exit-code pinpoint is intentionally dropped (the
// abort reports the file, not the row; drew-t2-conversion-spec sec.5).
// `package main` + bare `import tok/lex` mirrors wwdump (the only other
// external lex consumer).
package syntax_test;
@@ -205,8 +202,7 @@ fn checkkw(s: str, want: tkind) void = {
};
};
// checkprint — tokprint `t` to a freshly-rewound fd, read the bytes
// back, and assert they equal `want`. The fd is RDWR; we lseek to 0
// The fd is RDWR; we lseek to 0
// before each write so earlier (possibly longer) content past want.len
// is irrelevant — only want.len bytes from offset 0 are compared.
fn checkprint(fd: i32, t: *tok, want: str) void = {

View File

@@ -1,6 +1,6 @@
// lib/ww/syntax/typ.ww — port of cmd/wcc/type.c.
// Port of cmd/wcc/type.c.
//
// Status: full structural port. The C version uses module-globals for
// The C version uses module-globals for
// the primitive types (tyvoid, tyi32, …); ww doesn't have writable
// global storage yet, so we bundle the primitives into a `tctx` that
// the checker passes around explicitly. typesinit fills the tctx
@@ -10,11 +10,6 @@ package syntax;
import os;
// ---- TypeKind ---------------------------------------------------------
// Numeric values must stay aligned with cmd/wcc/ww.h TypeKind so the
// next diff signal (typed-AST printer / cgen) can compare across the
// two implementations.
// Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values
// are explicit and must stay in sync — the selfhost selfcheck and
// typed-AST printers depend on matching numeric layout.
@@ -67,8 +62,6 @@ export type tykind = enum i32 {
// fabricate a 0-byte slot. Value == U64_MAX.
def SIZE_UNDEFINED: u64 = 18446744073709551615;
// ---- tinfo / tfield / tparam -----------------------------------------
export type tfield = struct {
name: str,
type_: *tinfo,
@@ -173,8 +166,6 @@ export type tinfocacheent = struct {
// scaled up — sym's 16 would give ~340-deep chains here.
def NBUCKETS_TINFO: u64 = 8192u64;
// ---- tctx — the box of primitive types -------------------------------
export type tctx = struct {
tyvoid: *tinfo,
tybool: *tinfo,
@@ -206,8 +197,6 @@ export type tctx = struct {
tinfobuckets: **tinfocacheent, // length NBUCKETS_TINFO; node-ptr hash index
};
// ---- constructors -----------------------------------------------------
export fn newtype(k: tykind) *tinfo = {
let t: *tinfo = alloc(tinfo{kind=k, size=0u64, align=0u64, sub=nil, alen=0u64, fields=nil, params=nil, tupleelems=nil, ret=nil, variadic=0, nullable=0, name="", under=nil, slotsize=0u64, packed=0})!;
return t;
@@ -357,8 +346,6 @@ export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
c.tinfobuckets[bi] = e;
};
// ---- predicates -------------------------------------------------------
export fn typeisint(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;