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:
@@ -1,6 +1,5 @@
|
|||||||
// asciitest — exercises lib/ascii case folding. Run with
|
// A failing row aborts via the assert/abort builtin (task #5 @test
|
||||||
// `ww run lib/ascii/asciitest.ww`. A failing row aborts via the
|
// conversion).
|
||||||
// assert/abort builtin (task #5 @test conversion).
|
|
||||||
//
|
//
|
||||||
// Vectors mirror Hare's @test fn strcasecmp in ref/hare/ascii/string.ha.
|
// Vectors mirror Hare's @test fn strcasecmp in ref/hare/ascii/string.ha.
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
// scannertest — exercises the lib/bufio scanner surface. The @tests
|
// Row ownership mirrors ref/hare/bufio/scanner_test+test.ha.
|
||||||
// enumerate parallel `[N]T` arrays of inputs and expectations, then
|
// Streams are built as `let st = memio.fixed(buf); &st.vt` — the #94
|
||||||
// iterate one body across them. #94 fold-eFinal: the unified
|
// unified value-return io surface (read/write/close return size/io.error).
|
||||||
// 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.
|
|
||||||
|
|
||||||
package bufio_test;
|
package bufio_test;
|
||||||
|
|
||||||
@@ -12,8 +9,6 @@ import encoding.utf8;
|
|||||||
import io;
|
import io;
|
||||||
import memio;
|
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 = {
|
fn putstr(s: str, into: []u8, off: i32) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < s.len) {
|
for (i < s.len) {
|
||||||
@@ -23,8 +18,6 @@ fn putstr(s: str, into: []u8, off: i32) i32 = {
|
|||||||
return off + s.len;
|
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;
|
let errvt: io.vtable;
|
||||||
|
|
||||||
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||||
@@ -244,7 +237,6 @@ fn errsource() io.stream = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@test fn boundarycases() void = {
|
@test fn boundarycases() void = {
|
||||||
// ---- empty line via scanline -----------------------------------
|
|
||||||
let s1: [16]u8;
|
let s1: [16]u8;
|
||||||
let n1: i32 = putstr("\nfoo\n", s1[0:16], 0);
|
let n1: i32 = putstr("\nfoo\n", s1[0:16], 0);
|
||||||
let m1mem: memio.stream = memio.fixed(s1[0:n1]);
|
let m1mem: memio.stream = memio.fixed(s1[0:n1]);
|
||||||
@@ -271,7 +263,6 @@ fn errsource() io.stream = {
|
|||||||
};
|
};
|
||||||
bufio.finish(&sc1);
|
bufio.finish(&sc1);
|
||||||
|
|
||||||
// ---- scanbytes idempotent EOF ---------------------------------
|
|
||||||
let s2: [8]u8;
|
let s2: [8]u8;
|
||||||
let n2: i32 = putstr("ab", s2[0:8], 0);
|
let n2: i32 = putstr("ab", s2[0:8], 0);
|
||||||
let m2mem: memio.stream = memio.fixed(s2[0:n2]);
|
let m2mem: memio.stream = memio.fixed(s2[0:n2]);
|
||||||
|
|||||||
@@ -2,24 +2,6 @@
|
|||||||
// Hare's bufio:: surface (ref/hare/bufio/{scanner,stream}.ha). Project
|
// Hare's bufio:: surface (ref/hare/bufio/{scanner,stream}.ha). Project
|
||||||
// #94 fold-eFinal.
|
// #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
|
// VALUE-RETURN (Hare ref/hare/bufio/stream.ha:69 init, scanner.ha:92
|
||||||
// newscanner_buf): each constructor builds in a local `let r: T;`,
|
// newscanner_buf): each constructor builds in a local `let r: T;`,
|
||||||
// field-assigns every slot, and `return r;`. The caller owns the
|
// field-assigns every slot, and `return r;`. The caller owns the
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// streamtest — exercises the lib/bufio buffered stream surface. The
|
// Row ownership mirrors ref/hare/bufio/stream_test+test.ha.
|
||||||
// stream @tests are one scenario per fn. #94 fold-eFinal: the unified
|
// Streams are built as `let st = memio.fixed(buf); &st.vt` — the #94
|
||||||
// value-return surface — `let st = memio.fixed(buf); &st.vt` into the
|
// unified value-return io surface (read/write/close return size/io.error).
|
||||||
// stream init, io.read/write/close return size/io.error. Row
|
|
||||||
// ownership mirrors ref/hare/bufio/stream_test+test.ha.
|
|
||||||
|
|
||||||
package bufio_test;
|
package bufio_test;
|
||||||
|
|
||||||
@@ -10,8 +8,6 @@ import bufio;
|
|||||||
import io;
|
import io;
|
||||||
import memio;
|
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 = {
|
fn sputstr(s: str, into: []u8, off: i32) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < s.len) {
|
for (i < s.len) {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// bytes — slice operations over []u8. Mirrors Hare's bytes module
|
// Hare port of the in-tree subset; see ref/hare/bytes/.
|
||||||
// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix
|
|
||||||
// helpers used by lib/encoding, lib/bufio, lib/memio.
|
|
||||||
//
|
//
|
||||||
// Documented divergences from Hare:
|
// Documented divergences from Hare:
|
||||||
// - index_slice / rindex_slice use naive O(n·m); Hare specialises
|
// - 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.
|
// (utf8.ww:36). Plain `void` (not `!void`): continuation signal.
|
||||||
export type done = void;
|
export type done = void;
|
||||||
|
|
||||||
// tokenizer — cursor over an input slice. Layout mirrors
|
// Layout mirrors ref/hare/bytes/tokenize.ha:6-10. `p` is the cached
|
||||||
// ref/hare/bytes/tokenize.ha:6-10. `p` is the cached peek-position;
|
// peek-position; I64_MAX (forward) / I64_MIN (reverse) are the
|
||||||
// I64_MAX (forward) / I64_MIN (reverse) are the unprimed sentinels.
|
// unprimed sentinels. p < 0 also identifies a reverse-direction
|
||||||
// p < 0 also identifies a reverse-direction iterator.
|
// iterator.
|
||||||
export type tokenizer = struct {
|
export type tokenizer = struct {
|
||||||
in: []u8,
|
in: []u8,
|
||||||
delim: []u8,
|
delim: []u8,
|
||||||
p: i64,
|
p: i64,
|
||||||
};
|
};
|
||||||
|
|
||||||
// equal — true iff `a` and `b` have the same length and contents.
|
|
||||||
// ref/hare/bytes/equal.ha:9.
|
// ref/hare/bytes/equal.ha:9.
|
||||||
export fn equal(a: []u8, b: []u8) bool = {
|
export fn equal(a: []u8, b: []u8) bool = {
|
||||||
if (a.len != b.len) { return false; };
|
if (a.len != b.len) { return false; };
|
||||||
@@ -51,8 +48,6 @@ export fn equal(a: []u8, b: []u8) bool = {
|
|||||||
return true;
|
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.
|
// ref/hare/bytes/index.ha:6.
|
||||||
export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
||||||
match (needle) {
|
match (needle) {
|
||||||
@@ -85,9 +80,8 @@ export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// rindex — last offset of `needle` in `s`. Empty []u8 needle returns
|
// Empty []u8 needle returns s.len (ref/hare/bytes/index.ha:103 —
|
||||||
// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0).
|
// Hare's loop yields r-0 at i=0). ref/hare/bytes/index.ha:86.
|
||||||
// ref/hare/bytes/index.ha:86.
|
|
||||||
export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
||||||
match (needle) {
|
match (needle) {
|
||||||
case let c: u8 => {
|
case let c: u8 => {
|
||||||
@@ -118,7 +112,6 @@ export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// contains — true iff any of `needles` (byte or sub-slice) appears in `s`.
|
|
||||||
// ref/hare/bytes/contains.ha:6.
|
// ref/hare/bytes/contains.ha:6.
|
||||||
export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
|
export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -142,8 +135,7 @@ export fn contains(s: []u8, needles: (u8 | []u8)...) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ltrim — borrowed view of `in` with leading bytes in `trim` stripped.
|
// ref/hare/bytes/trim.ha:7. Borrowed view of `in` — caller must not free.
|
||||||
// `trim` must be non-empty. ref/hare/bytes/trim.ha:7.
|
|
||||||
export fn ltrim(in: []u8, trim: u8...) []u8 = {
|
export fn ltrim(in: []u8, trim: u8...) []u8 = {
|
||||||
assert(trim.len > 0, "bytes.ltrim called with empty trim set");
|
assert(trim.len > 0, "bytes.ltrim called with empty trim set");
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -155,8 +147,7 @@ export fn ltrim(in: []u8, trim: u8...) []u8 = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// rtrim — borrowed view of `in` with trailing bytes in `trim` stripped.
|
// ref/hare/bytes/trim.ha:17. Borrowed view of `in`. Hare's loop uses
|
||||||
// `trim` must be non-empty. ref/hare/bytes/trim.ha:17. Hare's loop uses
|
|
||||||
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
|
// `size` underflow at i==0 to terminate; ww indices are signed i32, so
|
||||||
// the equivalent termination is spelled `i >= 0` explicitly.
|
// the equivalent termination is spelled `i >= 0` explicitly.
|
||||||
export fn rtrim(in: []u8, trim: u8...) []u8 = {
|
export fn rtrim(in: []u8, trim: u8...) []u8 = {
|
||||||
@@ -170,13 +161,11 @@ export fn rtrim(in: []u8, trim: u8...) []u8 = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// trim — borrowed view of `in` with both ends in `trim` stripped.
|
// ref/hare/bytes/trim.ha:27. Borrowed view of `in`.
|
||||||
// ref/hare/bytes/trim.ha:27.
|
|
||||||
export fn trim(in: []u8, trim: u8...) []u8 = {
|
export fn trim(in: []u8, trim: u8...) []u8 = {
|
||||||
return ltrim(rtrim(in, trim...), trim...);
|
return ltrim(rtrim(in, trim...), trim...);
|
||||||
};
|
};
|
||||||
|
|
||||||
// hasprefix — true iff `s` starts with `pre`.
|
|
||||||
// ref/hare/bytes/contains.ha:21.
|
// ref/hare/bytes/contains.ha:21.
|
||||||
export fn hasprefix(s: []u8, pre: []u8) bool = {
|
export fn hasprefix(s: []u8, pre: []u8) bool = {
|
||||||
if (pre.len > s.len) { return false; };
|
if (pre.len > s.len) { return false; };
|
||||||
@@ -188,7 +177,6 @@ export fn hasprefix(s: []u8, pre: []u8) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// hassuffix — true iff `s` ends with `suf`.
|
|
||||||
// ref/hare/bytes/contains.ha:35.
|
// ref/hare/bytes/contains.ha:35.
|
||||||
export fn hassuffix(s: []u8, suf: []u8) bool = {
|
export fn hassuffix(s: []u8, suf: []u8) bool = {
|
||||||
if (suf.len > s.len) { return false; };
|
if (suf.len > s.len) { return false; };
|
||||||
@@ -201,7 +189,7 @@ export fn hassuffix(s: []u8, suf: []u8) bool = {
|
|||||||
return true;
|
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 = {
|
export fn reverse(s: []u8) void = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
let j: i32 = s.len - 1;
|
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 = {
|
export fn zero(s: []u8) void = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < s.len) {
|
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
|
// `delim` is borrowed; caller keeps it valid for the tokenizer's
|
||||||
// lifetime. ref/hare/bytes/tokenize.ha:22.
|
// lifetime. ref/hare/bytes/tokenize.ha:22.
|
||||||
export fn tokenize(in: []u8, delim: u8...) tokenizer = {
|
export fn tokenize(in: []u8, delim: u8...) tokenizer = {
|
||||||
@@ -242,8 +228,7 @@ export fn tokenize(in: []u8, delim: u8...) tokenizer = {
|
|||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
// rtokenize — reverse-direction tokenize. First nexttoken yields the
|
// ref/hare/bytes/tokenize.ha:40.
|
||||||
// last token, last nexttoken yields the first. ref/hare/bytes/tokenize.ha:40.
|
|
||||||
export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
|
export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
|
||||||
assert(delim.len > 0, "bytes.rtokenize called with empty slice");
|
assert(delim.len > 0, "bytes.rtokenize called with empty slice");
|
||||||
assert((in.len: i64) < types.I64_MAX,
|
assert((in.len: i64) < types.I64_MAX,
|
||||||
@@ -259,9 +244,8 @@ export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
|
|||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
// peektoken — next token without advancing the cursor. Returns done
|
// Returns done once `s.delim` has been zeroed by a prior past-end
|
||||||
// once `s.delim` has been zeroed by a prior past-end nexttoken.
|
// nexttoken. ref/hare/bytes/tokenize.ha:91.
|
||||||
// ref/hare/bytes/tokenize.ha:91.
|
|
||||||
export fn peektoken(s: *tokenizer) ([]u8 | done) = {
|
export fn peektoken(s: *tokenizer) ([]u8 | done) = {
|
||||||
if (s.delim.len == 0) {
|
if (s.delim.len == 0) {
|
||||||
let d: done; return d;
|
let d: done; return d;
|
||||||
@@ -338,7 +322,6 @@ export fn peektoken(s: *tokenizer) ([]u8 | done) = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// nexttoken — current token, then advance past it and the delim.
|
|
||||||
// Once the input is exhausted, returns done and zeros `s.delim` so
|
// Once the input is exhausted, returns done and zeros `s.delim` so
|
||||||
// subsequent peeks short-circuit. ref/hare/bytes/tokenize.ha:59.
|
// subsequent peeks short-circuit. ref/hare/bytes/tokenize.ha:59.
|
||||||
export fn nexttoken(s: *tokenizer) ([]u8 | done) = {
|
export fn nexttoken(s: *tokenizer) ([]u8 | done) = {
|
||||||
@@ -380,15 +363,13 @@ export fn nexttoken(s: *tokenizer) ([]u8 | done) = {
|
|||||||
return b;
|
return b;
|
||||||
};
|
};
|
||||||
|
|
||||||
// remainingtokens — the unconsumed portion of `s.in`. Read-only view.
|
// ref/hare/bytes/tokenize.ha:145. Read-only borrowed view.
|
||||||
// ref/hare/bytes/tokenize.ha:145.
|
|
||||||
export fn remainingtokens(s: *tokenizer) []u8 = {
|
export fn remainingtokens(s: *tokenizer) []u8 = {
|
||||||
return s.in;
|
return s.in;
|
||||||
};
|
};
|
||||||
|
|
||||||
// splitn — split `in` on any byte in `delim`, returning up to `n`
|
// The trailing slot (when more than `n - 1` tokens exist) holds the
|
||||||
// tokens via forward iteration. The trailing slot (when more than
|
// unconsumed remainder.
|
||||||
// `n - 1` tokens exist) holds the unconsumed remainder.
|
|
||||||
//
|
//
|
||||||
// The caller frees the returned slice via
|
// The caller frees the returned slice via
|
||||||
// `os.free(r.ptr: *void, (r.cap: u64) * 24u64)`. Element bytes are
|
// `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;
|
return toks;
|
||||||
};
|
};
|
||||||
|
|
||||||
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
|
// The trailing slot holds the unconsumed prefix (everything before
|
||||||
// collected from the end of `in`. The trailing slot holds the
|
// the n-th-from-last delim hit).
|
||||||
// unconsumed prefix (everything before the n-th-from-last delim hit).
|
|
||||||
//
|
//
|
||||||
// When the input has fewer than n tokens, the `done` short-circuit
|
// When the input has fewer than n tokens, the `done` short-circuit
|
||||||
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
|
// 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;
|
return toks;
|
||||||
};
|
};
|
||||||
|
|
||||||
// split — full split of `in` on `delim` (no token cap). Mirrors
|
// Mirrors `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
|
||||||
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
|
|
||||||
// because the index type is i32 (lib/CLAUDE.md).
|
// because the index type is i32 (lib/CLAUDE.md).
|
||||||
//
|
//
|
||||||
// ref/hare/bytes/tokenize.ha:225.
|
// 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);
|
return splitn(in, delim, types.I32_MAX);
|
||||||
};
|
};
|
||||||
|
|
||||||
// cut — split `in` along the first instance of `delim`, returning the
|
// When `delim` is absent, the whole input is the first half and the
|
||||||
// portion before and the portion after the delimiter as a borrowed
|
// second is empty. Both halves are borrowed views — caller must not
|
||||||
// tuple. When `delim` is absent, the whole input is the first half and
|
// free them. ref/hare/bytes/tokenize.ha:392.
|
||||||
// the second is empty. ref/hare/bytes/tokenize.ha:392.
|
|
||||||
//
|
//
|
||||||
// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91);
|
// 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
|
// 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.
|
// ref/hare/bytes/tokenize.ha:413.
|
||||||
export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = {
|
||||||
let ln: i32 = match (delim) {
|
let ln: i32 = match (delim) {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// containstest — exercises bytes.contains/hasprefix/hassuffix. A
|
// Vectors mirror ref/hare/bytes/contains.ha (task #5 @test conversion).
|
||||||
// failing row aborts via the assert/abort builtin (task #5 @test
|
|
||||||
// conversion). Vectors mirror ref/hare/bytes/contains.ha.
|
|
||||||
|
|
||||||
package bytes_test;
|
package bytes_test;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// equaltest — exercises bytes.equal. A failing row aborts via the
|
// Vectors mirror ref/hare/bytes/equal.ha (task #5 @test conversion).
|
||||||
// assert/abort builtin (task #5 @test conversion).
|
|
||||||
// Vectors mirror ref/hare/bytes/equal.ha.
|
|
||||||
|
|
||||||
package bytes_test;
|
package bytes_test;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// indextest — exercises bytes.index/rindex, u8 and []u8 arms. A
|
// Vectors mirror ref/hare/bytes/index.ha (task #5 @test conversion).
|
||||||
// failing row aborts via the assert/abort builtin (task #5 @test
|
|
||||||
// conversion). Vectors mirror ref/hare/bytes/index.ha.
|
|
||||||
|
|
||||||
package bytes_test;
|
package bytes_test;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// tokenizetest — exercises the bytes tokenize/splitn/cut families.
|
// Vectors mirror ref/hare/bytes/tokenize.ha (task #5 @test conversion).
|
||||||
// A failing row aborts via the assert/abort builtin (task #5 @test
|
|
||||||
// conversion). Vectors mirror ref/hare/bytes/tokenize.ha.
|
|
||||||
|
|
||||||
package bytes_test;
|
package bytes_test;
|
||||||
|
|
||||||
@@ -11,8 +9,6 @@ import os;
|
|||||||
// drives the iterator through an expected-token sequence and asserts
|
// drives the iterator through an expected-token sequence and asserts
|
||||||
// `equal(p, n)` (peek == next), `equal(n, want)` (next == expected).
|
// `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 = {
|
fn expect_token(t: *bytes.tokenizer, want: []u8) void = {
|
||||||
match (bytes.peektoken(t)) {
|
match (bytes.peektoken(t)) {
|
||||||
case let p: []u8 => {
|
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 = {
|
fn expect_done(t: *bytes.tokenizer) void = {
|
||||||
match (bytes.peektoken(t)) {
|
match (bytes.peektoken(t)) {
|
||||||
case let p: []u8 => abort();
|
case let p: []u8 => abort();
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// trimtest — exercises bytes.ltrim/rtrim/trim. A failing row aborts
|
// Vectors mirror ref/hare/bytes/trim.ha (task #5 @test conversion).
|
||||||
// via the assert/abort builtin (task #5 @test conversion).
|
|
||||||
// Vectors mirror ref/hare/bytes/trim.ha.
|
|
||||||
|
|
||||||
package bytes_test;
|
package bytes_test;
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
// These declarations cover the small surface the bootstrap wants:
|
||||||
// process exit, three io syscalls, and the malloc/free pair. Higher
|
// 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;
|
package libc;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
// sha256_test — exercises lib/crypto/sha256 against the standard NIST
|
// Vectors: the standard NIST SHA-256 set (FIPS 180-4 examples + the
|
||||||
// SHA-256 vectors (FIPS 180-4 examples + the classic "one million a's").
|
// classic "one million a's").
|
||||||
// Run with `out/bin/ww run lib/crypto/sha256/sha256_test.ww`.
|
|
||||||
//
|
//
|
||||||
// The digest is the cgen-correctness oracle for u32 wrapping arithmetic
|
// The digest is the cgen-correctness oracle for u32 wrapping arithmetic
|
||||||
// + the hash-vtable dispatch: any u32-overflow / rotate miscompile shows
|
// + the hash-vtable dispatch: any u32-overflow / rotate miscompile shows
|
||||||
// up as a byte mismatch. A failing row aborts via the assert/abort
|
// up as a byte mismatch. A failing row aborts via the assert/abort
|
||||||
// builtin (task #5 @test conversion). Expected digests come through the
|
// builtin (task #5 @test conversion). Expected digests come through the
|
||||||
// (separately tested)
|
// (separately tested) hex.decodestr so the vectors stay readable.
|
||||||
// hex.decodestr so the vectors stay readable.
|
|
||||||
package sha256_test;
|
package sha256_test;
|
||||||
|
|
||||||
import bytes;
|
import bytes;
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
// dirs — XDG base directory paths. Port of Hare's lib/dirs
|
// dirs — XDG base directory paths. Port of Hare's lib/dirs
|
||||||
// (ref/hare/dirs/xdg.ha) using the lib/temp-style static `[256]u8`
|
// (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
|
// pathbuf in place of Hare's `path::buffer` (lib/path doesn't ship
|
||||||
// a buffer type yet).
|
// a buffer type yet). Surface is 1:1 with Hare's xdg.ha minus runtime().
|
||||||
//
|
|
||||||
// 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>
|
|
||||||
//
|
//
|
||||||
// Returns are static-buffer views — borrowed for the lifetime of
|
// Returns are static-buffer views — borrowed for the lifetime of
|
||||||
// the next dirs call (any of the four above). Callers needing the
|
// 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.
|
// octal literals, so we name the constant once.
|
||||||
def MODE_0755: i32 = 493i32;
|
def MODE_0755: i32 = 493i32;
|
||||||
|
|
||||||
// puts — append `s` to pathbuf at offset `off`, capping against the
|
// Caps at 255 to leave room for the trailing NUL. Same shape as
|
||||||
// buffer's capacity to leave room for the trailing NUL. Returns the
|
// lib/temp.puts.
|
||||||
// new offset. Same shape as lib/temp.puts.
|
|
||||||
fn puts(off: i32, s: str) i32 = {
|
fn puts(off: i32, s: str) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < s.len) {
|
for (i < s.len) {
|
||||||
@@ -84,9 +76,6 @@ fn puts(off: i32, s: str) i32 = {
|
|||||||
return off + i;
|
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]]
|
// Embedded '/' in `sub` (e.g. ".local/share") is fine — [[os.mkdirs]]
|
||||||
// handles intermediate dirs.
|
// handles intermediate dirs.
|
||||||
fn build(base: str, sub: str, prog: str) void = {
|
fn build(base: str, sub: str, prog: str) void = {
|
||||||
@@ -112,8 +101,7 @@ fn build(base: str, sub: str, prog: str) void = {
|
|||||||
pathlen = off;
|
pathlen = off;
|
||||||
};
|
};
|
||||||
|
|
||||||
// view — return a `str` view into pathbuf[0..pathlen]. Borrowed
|
// Borrowed for the lifetime of the next dirs call.
|
||||||
// for the lifetime of the next dirs call.
|
|
||||||
fn view() str = {
|
fn view() str = {
|
||||||
let r: str;
|
let r: str;
|
||||||
r.ptr = &pathbuf[0];
|
r.ptr = &pathbuf[0];
|
||||||
|
|||||||
@@ -92,8 +92,6 @@ fn gettmpdir() str = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- 1: XDG_CONFIG_HOME=<tmpl>/cfg → <tmpl>/cfg/myapp --------------
|
|
||||||
|
|
||||||
@test fn test_config_xdg_set() void = {
|
@test fn test_config_xdg_set() void = {
|
||||||
let tmpl = gettmpdir();
|
let tmpl = gettmpdir();
|
||||||
let exp = expected(tmpl, "cfg", "myapp");
|
let exp = expected(tmpl, "cfg", "myapp");
|
||||||
@@ -101,8 +99,6 @@ fn gettmpdir() str = {
|
|||||||
assert(!(!streq(got, exp)));
|
assert(!(!streq(got, exp)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- 2: XDG_CACHE_HOME="relative/path" → fallback to $HOME/.cache --
|
|
||||||
//
|
|
||||||
// Exercises Hare's `if (!path::abs(path)) yield;` branch — a
|
// Exercises Hare's `if (!path::abs(path)) yield;` branch — a
|
||||||
// non-absolute XDG_*_HOME silently falls through to the HOME path.
|
// non-absolute XDG_*_HOME silently falls through to the HOME path.
|
||||||
|
|
||||||
@@ -113,8 +109,6 @@ fn gettmpdir() str = {
|
|||||||
assert(!(!streq(got, exp)));
|
assert(!(!streq(got, exp)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- 3: XDG_DATA_HOME unset → fallback to $HOME/.local/share -------
|
|
||||||
|
|
||||||
@test fn test_data_unset_fallback() void = {
|
@test fn test_data_unset_fallback() void = {
|
||||||
let tmpl = gettmpdir();
|
let tmpl = gettmpdir();
|
||||||
let exp = expected(tmpl, ".local/share", "myapp");
|
let exp = expected(tmpl, ".local/share", "myapp");
|
||||||
@@ -122,8 +116,6 @@ fn gettmpdir() str = {
|
|||||||
assert(!(!streq(got, exp)));
|
assert(!(!streq(got, exp)));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- 4: XDG_STATE_HOME unset → fallback to $HOME/.local/state ------
|
|
||||||
|
|
||||||
@test fn test_state_unset_fallback() void = {
|
@test fn test_state_unset_fallback() void = {
|
||||||
let tmpl = gettmpdir();
|
let tmpl = gettmpdir();
|
||||||
let exp = expected(tmpl, ".local/state", "myapp");
|
let exp = expected(tmpl, ".local/state", "myapp");
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// encoding/base32 — RFC 4648 base32 encode/decode, buffer-based.
|
|
||||||
//
|
|
||||||
// Mirrors Hare's encoding::base32 surface, modulo Hare's stream-based
|
// Mirrors Hare's encoding::base32 surface, modulo Hare's stream-based
|
||||||
// encoder/decoder. ww ships the in-memory subset only: `encode(dst,
|
// encoder/decoder. ww ships the in-memory subset only: `encode(dst,
|
||||||
// src)` writes the encoded bytes into `dst`, returning the count;
|
// src)` writes the encoded bytes into `dst`, returning the count;
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ fn enchexvec(input: str, expect: str) void = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@test fn roundtrip_all_quintets() void = {
|
@test fn roundtrip_all_quintets() void = {
|
||||||
// Encode then decode every 5-byte combination of a small set.
|
|
||||||
let raw: [5]u8;
|
let raw: [5]u8;
|
||||||
raw[0] = 0x00u8;
|
raw[0] = 0x00u8;
|
||||||
raw[1] = 0x55u8;
|
raw[1] = 0x55u8;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
// base64_test — exercises lib/encoding/base64's io-streaming surface.
|
// Mirrors Hare's base64 @test fns (ref/hare/encoding/base64/base64.ha:
|
||||||
// Run with `out/bin/ww run lib/encoding/base64/base64_test.ww`. Mirrors
|
// 315,514,601) over the RFC 4648 §10 vectors, table-driven (parallel
|
||||||
// Hare's base64 @test fns (ref/hare/encoding/base64/base64.ha:315,514,
|
// arrays; tuple-row arrays are blocked by #111). A failing row aborts
|
||||||
// 601) over the RFC 4648 §10 vectors, table-driven (parallel arrays;
|
// via the assert/abort builtin (task #5 @test conversion).
|
||||||
// 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
|
// The streaming decoder (newdecoder) is deferred (#247-sibling), so the
|
||||||
// decode side is exercised through decodestr only.
|
// 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
|
// Inputs are the prefixes of "foobar". The §10 expected encodings
|
||||||
// contain no '+' / '/', so std and base64url agree on these vectors —
|
// contain no '+' / '/', so std and base64url agree on these vectors —
|
||||||
// both alphabets are driven over the same table here; the std-vs-url
|
// 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 '+'/'/',
|
// [0xFB, 0xFF, 0xBF] hits the 62/63 alphabet slots: std emits '+'/'/',
|
||||||
// url emits '-'/'_'. The two encodings must differ, each round-trips
|
// url emits '-'/'_'. The two encodings must differ, each round-trips
|
||||||
// under its own alphabet, and each is INVALID under the other (std
|
// 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);
|
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
|
// Table-driven (parallel-array idiom; tuple rows blocked by #111). Each
|
||||||
// row exercises one malformed class the hand-written validation in
|
// row exercises one malformed class the hand-written validation in
|
||||||
// decodestr must reject. Cross-alphabet chars are covered separately by
|
// 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 = {
|
@test fn sizes() void = {
|
||||||
assert(!(base64.encodedsize(0) != 0));
|
assert(!(base64.encodedsize(0) != 0));
|
||||||
assert(!(base64.encodedsize(1) != 4));
|
assert(!(base64.encodedsize(1) != 4));
|
||||||
@@ -172,8 +162,6 @@ fn inval_check(enc: *base64.encoding, encoded: str) void = {
|
|||||||
base64.decodedsize(5i32);
|
base64.decodedsize(5i32);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- round-trip every byte value 0..255 (std + url) ----
|
|
||||||
|
|
||||||
@test fn roundtrip_all_bytes() void = {
|
@test fn roundtrip_all_bytes() void = {
|
||||||
let src: [256]u8;
|
let src: [256]u8;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// hextest — exercises lib/encoding/hex's io-streaming surface. Run with
|
// Mirrors Hare's hex @test fns (ref/hare/encoding/hex/hex.ha:82,96,194)
|
||||||
// `out/bin/ww run lib/encoding/hex/hextest.ww`. Mirrors Hare's hex
|
// plus a full-byte round-trip. A failing row aborts via the assert/abort
|
||||||
// @test fns (ref/hare/encoding/hex/hex.ha:82,96,194) plus a full-byte
|
// builtin (task #5 @test conversion).
|
||||||
// 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
|
// The streaming decoder (newdecoder) is deferred (#247), so the decode
|
||||||
// side is exercised through decodestr only.
|
// side is exercised through decodestr only.
|
||||||
@@ -33,8 +31,6 @@ fn cafebabe() [8]u8 = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- encodestr ---- ref/hare/encoding/hex/hex.ha:82
|
|
||||||
|
|
||||||
@test fn encodestr_basic() void = {
|
@test fn encodestr_basic() void = {
|
||||||
let in: [8]u8 = cafebabe();
|
let in: [8]u8 = cafebabe();
|
||||||
assert(!(!streq(hex.encodestr(in[0:8]), "cafebabedeadf00d")));
|
assert(!(!streq(hex.encodestr(in[0:8]), "cafebabedeadf00d")));
|
||||||
@@ -57,8 +53,6 @@ fn cafebabe() [8]u8 = {
|
|||||||
assert(!(hex.encodestr(in[0:0]).len != 0));
|
assert(!(hex.encodestr(in[0:0]).len != 0));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- encode (io.handle sink) ---- ref/hare/encoding/hex/hex.ha:96
|
|
||||||
|
|
||||||
@test fn encode_stream() void = {
|
@test fn encode_stream() void = {
|
||||||
let in: [8]u8 = cafebabe();
|
let in: [8]u8 = cafebabe();
|
||||||
let out: memio.stream = memio.dynamic();
|
let out: memio.stream = memio.dynamic();
|
||||||
@@ -69,8 +63,6 @@ fn cafebabe() [8]u8 = {
|
|||||||
assert(!(!streq(memio.string(&out), "cafebabedeadf00d")));
|
assert(!(!streq(memio.string(&out), "cafebabedeadf00d")));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- decodestr round-trip ---- ref/hare/encoding/hex/hex.ha:194
|
|
||||||
|
|
||||||
@test fn decodestr_lower() void = {
|
@test fn decodestr_lower() void = {
|
||||||
match (hex.decodestr("cafebabedeadf00d")) {
|
match (hex.decodestr("cafebabedeadf00d")) {
|
||||||
case let b: []u8 => {
|
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
|
||||||
//
|
// (ref/hare/encoding/hex/hex.ha:154,199).
|
||||||
// Odd length and non-hex chars both return errors.invalid.
|
|
||||||
|
|
||||||
@test fn decodestr_odd() void = {
|
@test fn decodestr_odd() void = {
|
||||||
match (hex.decodestr("abc")) {
|
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 = {
|
@test fn roundtrip_all_bytes() void = {
|
||||||
let src: [256]u8;
|
let src: [256]u8;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// decodetest — exercises the utf8 decoder: next/prev/validate/
|
// Vectors mirror ref/hare/encoding/utf8/decode.ha. A failing row aborts
|
||||||
// remaining/slice/position, plus the encode→decode round-trip. A
|
// via the assert/abort builtin (task #5 @test conversion).
|
||||||
// failing row aborts via the assert/abort builtin (task #5 @test
|
|
||||||
// conversion). Vectors mirror ref/hare/encoding/utf8/decode.ha.
|
|
||||||
|
|
||||||
package utf8_test;
|
package utf8_test;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// encodetest — exercises utf8.encoderune. A failing row aborts via
|
// Vectors mirror ref/hare/encoding/utf8/encode.ha. A failing row aborts
|
||||||
// the assert/abort builtin (task #5 @test conversion).
|
// via the assert/abort builtin (task #5 @test conversion).
|
||||||
// Vectors mirror ref/hare/encoding/utf8/encode.ha.
|
|
||||||
|
|
||||||
package utf8_test;
|
package utf8_test;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// runetest — exercises utf8.runesz/utf8sz. A failing row aborts via
|
// Vectors mirror ref/hare/encoding/utf8/rune.ha. A failing row aborts
|
||||||
// the assert/abort builtin (task #5 @test conversion).
|
// via the assert/abort builtin (task #5 @test conversion).
|
||||||
// Vectors mirror ref/hare/encoding/utf8/rune.ha.
|
|
||||||
|
|
||||||
package utf8_test;
|
package utf8_test;
|
||||||
|
|
||||||
|
|||||||
@@ -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).
|
// assert/abort builtin (task #5 @test conversion).
|
||||||
// Mirrors ref/hare/encoding/utf8/types.ha.
|
|
||||||
|
|
||||||
package utf8_test;
|
package utf8_test;
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha.
|
||||||
//
|
//
|
||||||
// The decoder is Hoehrmann's branchless DFA, originally published
|
// 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 = {
|
export fn position(d: *decoder) i32 = {
|
||||||
return d.offs: i32;
|
return d.offs: i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// endian — byte-order conversions. Mirrors Hare's endian:: surface:
|
// Mirrors Hare's endian:: surface: big-endian (be*), little-endian
|
||||||
// big-endian (be*), little-endian (le*), and network-order (hton/ntoh)
|
// (le*), and network-order (hton/ntoh) helpers. Host order on amd64 is
|
||||||
// helpers. Host order on amd64 is little-endian, so hton/ntoh are
|
// little-endian, so hton/ntoh are byte swaps and the le* family is
|
||||||
// byte swaps and the le* family is identity.
|
// identity.
|
||||||
|
|
||||||
package endian;
|
package endian;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// Parallel `[N]T` arrays of (errno, expected-tag) rather than a
|
||||||
// `[N]struct{...}` table — the cstage cgen's chained `arr[i].field`
|
// `[N]struct{...}` table — the cstage cgen's chained `arr[i].field`
|
||||||
// store gap (task #6). [[errtag]] collapses each named-void variant to
|
// 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 = {
|
@test fn mapping() void = {
|
||||||
let ins: [12]os.errno;
|
let ins: [12]os.errno;
|
||||||
ins[0] = os.ECONNREFUSED;
|
ins[0] = os.ECONNREFUSED;
|
||||||
@@ -85,7 +81,6 @@ fn errtag(e: errors.error) int = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- unmapped errno → opaque_ tail --------------------------------
|
|
||||||
@test fn opaquetail() void = {
|
@test fn opaquetail() void = {
|
||||||
// EIO (5) is outside the mapped set, so it wraps opaque_.
|
// EIO (5) is outside the mapped set, so it wraps opaque_.
|
||||||
let e: errors.error = errors.errno(5);
|
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 = {
|
@test fn strerrortext() void = {
|
||||||
assert(!(!streq(os.strerror(os.ENOENT), "No such file or directory")));
|
assert(!(!streq(os.strerror(os.ENOENT), "No such file or directory")));
|
||||||
assert(!(!streq(os.strerror(os.EINVAL), "Invalid argument")));
|
assert(!(!streq(os.strerror(os.EINVAL), "Invalid argument")));
|
||||||
|
|||||||
@@ -1,23 +1,6 @@
|
|||||||
// fmt — formatting writers. Mirrors Hare's lib/fmt subset. Project #94
|
// fmt — formatting writers. Mirrors Hare's lib/fmt subset (primary
|
||||||
// fold-eFinal; io fold-2 (#5) graduated the sink to [[io.handle]].
|
// surface ref/hare/fmt/print.ha:13). 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...`.
|
|
||||||
|
|
||||||
package fmt;
|
package fmt;
|
||||||
|
|
||||||
@@ -90,11 +73,6 @@ fn i64dec(v: i64) str = {
|
|||||||
// construction — both stages now accept bare int by MEMBERSHIP.
|
// construction — both stages now accept bare int by MEMBERSHIP.
|
||||||
export type formattable = (i64 | str | bool | rune | f64 | int | uint);
|
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) = {
|
fn putbytes(s: io.handle, p: *u8, n: i32) (size | io.error) = {
|
||||||
let v: []u8;
|
let v: []u8;
|
||||||
v.ptr = p;
|
v.ptr = p;
|
||||||
@@ -102,7 +80,6 @@ fn putbytes(s: io.handle, p: *u8, n: i32) (size | io.error) = {
|
|||||||
return io.write(s, v);
|
return io.write(s, v);
|
||||||
};
|
};
|
||||||
|
|
||||||
// writeone — emit one formattable through io.write.
|
|
||||||
fn writeone(s: io.handle, a: formattable) (size | io.error) = {
|
fn writeone(s: io.handle, a: formattable) (size | io.error) = {
|
||||||
match (a) {
|
match (a) {
|
||||||
case let n: i64 => {
|
case let n: i64 => {
|
||||||
@@ -145,8 +122,6 @@ fn writeone(s: io.handle, a: formattable) (size | io.error) = {
|
|||||||
return 0: size;
|
return 0: size;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- {n}-placeholder parser -----------------------------------------
|
|
||||||
//
|
|
||||||
// Mirrors ref/hare/fmt/{iter,print,wrappers}.ha. Format sequences:
|
// Mirrors ref/hare/fmt/{iter,print,wrappers}.ha. Format sequences:
|
||||||
//
|
//
|
||||||
// {} implicit-positional next arg
|
// {} 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`,
|
// fprint — write the formatted form of each `args` element to `s`,
|
||||||
// separated by spaces. Returns total bytes written or the first
|
// separated by spaces. Returns total bytes written or the first
|
||||||
// io.error. Mirrors ref/hare/fmt/print.ha (fprint) + wrappers.ha.
|
// 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);
|
return strings.frombytes(tight);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- process-stdio wrappers -----------------------------------------
|
// Mirror ref/hare/fmt/wrappers.ha. Hare routes the stdio wrappers through
|
||||||
//
|
|
||||||
// Mirror ref/hare/fmt/wrappers.ha. Hare routes these through
|
|
||||||
// os::stdout / os::stderr (io::handle); ww's os plays the sys role and
|
// 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
|
// 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
|
// (os.STD{OUT,ERR}_FILENO) and the io.file binding is cast at the call
|
||||||
|
|||||||
@@ -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
|
// Each @test writes through a memio.stream and compares the resulting
|
||||||
// bytes against an inline `want` literal. Bodies are short enough that
|
// bytes against an inline `want` literal. Bodies are short enough that
|
||||||
// the parallel-array idiom used by memiotest doesn't apply — every row
|
// 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;
|
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;
|
let errvt: io.vtable;
|
||||||
|
|
||||||
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
fn errread(s: io.stream, buf: []u8) (size | io.eof | io.error) = {
|
||||||
@@ -42,8 +35,6 @@ fn errsource() io.stream = {
|
|||||||
return &errvt;
|
return &errvt;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprint: bare str --------------------------------------------------
|
|
||||||
|
|
||||||
@test fn fprintbarestr() void = {
|
@test fn fprintbarestr() void = {
|
||||||
let mem: memio.stream = memio.dynamic();
|
let mem: memio.stream = memio.dynamic();
|
||||||
let s: io.stream = &mem.vt;
|
let s: io.stream = &mem.vt;
|
||||||
@@ -59,8 +50,6 @@ fn errsource() io.stream = {
|
|||||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprint: int + str mix, space-separated ----------------------------
|
|
||||||
|
|
||||||
@test fn fprintintstr() void = {
|
@test fn fprintintstr() void = {
|
||||||
let mem: memio.stream = memio.dynamic();
|
let mem: memio.stream = memio.dynamic();
|
||||||
let s: io.stream = &mem.vt;
|
let s: io.stream = &mem.vt;
|
||||||
@@ -76,8 +65,7 @@ fn errsource() io.stream = {
|
|||||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprint: i64::MIN / int MIN whole-magnitude (#67) ------------------
|
// #67: i64dec used `n = -n` within i64, which wraps at i64::MIN (-MIN == MIN),
|
||||||
// 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
|
// 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
|
// takes the magnitude through math.absi64 into u64. These rows pin the
|
||||||
// boundary value plus a normal negative, zero, and int MIN.
|
// 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(); };
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprint: bool + rune renders as "true A" --------------------------
|
|
||||||
|
|
||||||
@test fn fprintboolrune() void = {
|
@test fn fprintboolrune() void = {
|
||||||
let mem: memio.stream = memio.dynamic();
|
let mem: memio.stream = memio.dynamic();
|
||||||
let s: io.stream = &mem.vt;
|
let s: io.stream = &mem.vt;
|
||||||
@@ -131,8 +117,6 @@ fn errsource() io.stream = {
|
|||||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprint: zero args returns 0, no bytes written --------------------
|
|
||||||
|
|
||||||
@test fn fprintempty() void = {
|
@test fn fprintempty() void = {
|
||||||
let mem: memio.stream = memio.dynamic();
|
let mem: memio.stream = memio.dynamic();
|
||||||
let s: io.stream = &mem.vt;
|
let s: io.stream = &mem.vt;
|
||||||
@@ -148,8 +132,6 @@ fn errsource() io.stream = {
|
|||||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprintln: multi-arg, trailing '\n' --------------------------------
|
|
||||||
|
|
||||||
@test fn fprintlnmulti() void = {
|
@test fn fprintlnmulti() void = {
|
||||||
let mem: memio.stream = memio.dynamic();
|
let mem: memio.stream = memio.dynamic();
|
||||||
let s: io.stream = &mem.vt;
|
let s: io.stream = &mem.vt;
|
||||||
@@ -165,8 +147,6 @@ fn errsource() io.stream = {
|
|||||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprintln: zero args writes just the newline ----------------------
|
|
||||||
|
|
||||||
@test fn fprintlnempty() void = {
|
@test fn fprintlnempty() void = {
|
||||||
let mem: memio.stream = memio.dynamic();
|
let mem: memio.stream = memio.dynamic();
|
||||||
let s: io.stream = &mem.vt;
|
let s: io.stream = &mem.vt;
|
||||||
@@ -182,7 +162,6 @@ fn errsource() io.stream = {
|
|||||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
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
|
// memio.fixedwrite caps each call at the remaining buffer space and
|
||||||
// never closes the stream, so fprint sees a short i32 result, not
|
// never closes the stream, so fprint sees a short i32 result, not
|
||||||
// io.error. Verifies the inner-loop arithmetic adds the actual byte
|
// 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(); };
|
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
|
// Exercises the early-return arm in fprint's inner match — distinct from
|
||||||
// the short-write path above, which keeps returning i32 from a partial
|
// the short-write path above, which keeps returning i32 from a partial
|
||||||
// accept. Single arm is enough: every formattable case routes errors
|
// accept. Single arm is enough: every formattable case routes errors
|
||||||
@@ -220,10 +198,7 @@ fn errsource() io.stream = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprintf scenarios -------------------------------------------------
|
// Variadic call-site shape forces one body per shape; see the file header.
|
||||||
// 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.
|
|
||||||
|
|
||||||
@test fn fprintf_implicit() void = {
|
@test fn fprintf_implicit() void = {
|
||||||
let mem: memio.stream = memio.dynamic();
|
let mem: memio.stream = memio.dynamic();
|
||||||
@@ -303,8 +278,7 @@ fn errsource() io.stream = {
|
|||||||
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
match (c) { case void => {}; case let eioe: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fprint: rune args emit full UTF-8, not a truncated byte (#66) -----
|
// #66: writeone/formatraw's rune arm did `buf[0] = r: u8; putbytes(...,1)`,
|
||||||
// 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
|
// 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),
|
// through utf8.encoderune (ref/hare/fmt/print.ha:84). Edge runes: é (2B),
|
||||||
// € (3B), 😀 (4B), A (1B).
|
// € (3B), 😀 (4B), A (1B).
|
||||||
@@ -558,8 +532,6 @@ fn errsource() io.stream = {
|
|||||||
os.free(r.ptr: *void, r.len: u64);
|
os.free(r.ptr: *void, r.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- f64 dispatch arm -------------------------------------------------
|
|
||||||
//
|
|
||||||
// Pins the f64 formattable arm landed under task #17 (unblocked by #30
|
// Pins the f64 formattable arm landed under task #17 (unblocked by #30
|
||||||
// — the variant-widen-from-X0 fix). strconv.f64tos drives the render;
|
// — the variant-widen-from-X0 fix). strconv.f64tos drives the render;
|
||||||
// see lib/strconv/strconv.ww for the documented subset (fixed-point,
|
// see lib/strconv/strconv.ww for the documented subset (fixed-point,
|
||||||
|
|||||||
@@ -2,12 +2,6 @@
|
|||||||
// fnmatch:: (ref/hare/fnmatch/fnmatch.ha) using ww byte-indexed
|
// fnmatch:: (ref/hare/fnmatch/fnmatch.ha) using ww byte-indexed
|
||||||
// cursors in place of Hare's strings::iterator.
|
// 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):
|
// Matching rules (Hare-spec):
|
||||||
//
|
//
|
||||||
// - '?' matches any single byte
|
// - '?' matches any single byte
|
||||||
@@ -328,7 +322,6 @@ fn fnmatch_internal(pattern: str, string: str, fl: flag) (bool | invalid) = {
|
|||||||
let pp: i32 = 0;
|
let pp: i32 = 0;
|
||||||
let sp: i32 = 0;
|
let sp: i32 = 0;
|
||||||
|
|
||||||
// ---- prefix: match up to the first '*' ----------------------
|
|
||||||
let sawstar: bool = false;
|
let sawstar: bool = false;
|
||||||
for (!sawstar) {
|
for (!sawstar) {
|
||||||
let scur: i32 = sp;
|
let scur: i32 = sp;
|
||||||
@@ -361,7 +354,6 @@ fn fnmatch_internal(pattern: str, string: str, fl: flag) (bool | invalid) = {
|
|||||||
if (advance) { sp = scur; };
|
if (advance) { sp = scur; };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- find the tail (token count after the last '*') ---------
|
|
||||||
let pp_copy: i32 = pp;
|
let pp_copy: i32 = pp;
|
||||||
let pp_last: i32 = pp;
|
let pp_last: i32 = pp;
|
||||||
let pp_last_cnt: i32 = 0;
|
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;
|
let tail_pos: i32 = string.len - tail_cnt;
|
||||||
if (tail_pos < sp_copy) { return false; };
|
if (tail_pos < sp_copy) { return false; };
|
||||||
|
|
||||||
// ---- tail: match (from pp) against string[tail_pos..] -------
|
|
||||||
let ts: i32 = tail_pos;
|
let ts: i32 = tail_pos;
|
||||||
let tdone: bool = false;
|
let tdone: bool = false;
|
||||||
for (!tdone) {
|
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;
|
let mid_pat: str;
|
||||||
mid_pat.ptr = pattern.ptr + (pp_copy: u64);
|
mid_pat.ptr = pattern.ptr + (pp_copy: u64);
|
||||||
mid_pat.len = pp_last - pp_copy;
|
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;
|
let final: bool = false;
|
||||||
for (!final) {
|
for (!final) {
|
||||||
segstart_pat = pp;
|
segstart_pat = pp;
|
||||||
// Walk pattern until the next '/' (segment break) or end.
|
|
||||||
let inner: bool = true;
|
let inner: bool = true;
|
||||||
let kind: i32 = 0; // 0 = saw '/', 1 = saw end
|
let kind: i32 = 0; // 0 = saw '/', 1 = saw end
|
||||||
for (inner) {
|
for (inner) {
|
||||||
|
|||||||
@@ -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
|
// Table-driven via the [[check]] helper: every row is the uniform
|
||||||
// 4-tuple `(pattern, string, expected, flags)`. Cases follow Hare's
|
// 4-tuple `(pattern, string, expected, flags)`. Cases follow Hare's
|
||||||
// ref/hare/fnmatch/+test.ha (the de-facto spec for this port);
|
// 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));
|
assert(!(fnmatch.fnmatch(pat, s, f) != expected));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- basic literal / wildcard cases ---------------------------------
|
|
||||||
|
|
||||||
@test fn basic() void = {
|
@test fn basic() void = {
|
||||||
check("a", "a", true, 0);
|
check("a", "a", true, 0);
|
||||||
check("b", "b", 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);
|
check("*?*?*?*?", "abc", false, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- bracket expressions: literal / range / negation ----------------
|
|
||||||
|
|
||||||
@test fn brackets() void = {
|
@test fn brackets() void = {
|
||||||
check("[b]", "b", true, 0);
|
check("[b]", "b", true, 0);
|
||||||
check("a[b]c", "abc", 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);
|
check("[!-ac]", "b", true, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- POSIX character classes: [[:alnum:]] / [[:alpha:]] / ... -------
|
|
||||||
|
|
||||||
@test fn ctype() void = {
|
@test fn ctype() void = {
|
||||||
check("[[:alnum:]]", "7", true, 0);
|
check("[[:alnum:]]", "7", true, 0);
|
||||||
check("[[:alpha:]]", "[", false, 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);
|
check("[![:alnum:][:digit:]]", "a", false, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- flag.PERIOD: leading '.' must be literal in the pattern --------
|
|
||||||
|
|
||||||
@test fn period() void = {
|
@test fn period() void = {
|
||||||
let fp: i32 = 4; // flag.PERIOD
|
let fp: i32 = 4; // flag.PERIOD
|
||||||
check(".", ".", true, fp);
|
check(".", ".", true, fp);
|
||||||
@@ -152,16 +141,12 @@ fn check(pat: str, s: str, expected: bool, flags: i32) void = {
|
|||||||
check(".*", "asdf", false, fp);
|
check(".*", "asdf", false, fp);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- flag.NOESCAPE: '\\' loses its escape meaning -------------------
|
|
||||||
|
|
||||||
@test fn noescape() void = {
|
@test fn noescape() void = {
|
||||||
let nesc: i32 = 2; // flag.NOESCAPE
|
let nesc: i32 = 2; // flag.NOESCAPE
|
||||||
check("\\", "\\", true, nesc);
|
check("\\", "\\", true, nesc);
|
||||||
check("\\*", "\\asdf", true, nesc);
|
check("\\*", "\\asdf", true, nesc);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- musl-adapted cases (no flags) ---------------------------------
|
|
||||||
|
|
||||||
@test fn musl_basic() void = {
|
@test fn musl_basic() void = {
|
||||||
check("*.c", "foo.c", true, 0);
|
check("*.c", "foo.c", true, 0);
|
||||||
check("*.c", ".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);
|
check("[![:d-d]", "[", false, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- flag.PATHNAME: '/' must be matched by literal '/' --------------
|
|
||||||
|
|
||||||
@test fn pathname() void = {
|
@test fn pathname() void = {
|
||||||
let fp: i32 = 1; // flag.PATHNAME
|
let fp: i32 = 1; // flag.PATHNAME
|
||||||
check("[a-z]/[a-z]", "a/b", true, fp);
|
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);
|
check("???b", "aa/b", false, fp);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- flag.PERIOD + flag.PATHNAME combined ---------------------------
|
|
||||||
|
|
||||||
@test fn combined() void = {
|
@test fn combined() void = {
|
||||||
let pp: i32 = 1; // PATHNAME
|
let pp: i32 = 1; // PATHNAME
|
||||||
let fp: i32 = 4; // PERIOD
|
let fp: i32 = 4; // PERIOD
|
||||||
|
|||||||
@@ -403,8 +403,6 @@ export fn strerror(err: *error) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- help output --------------------------------------------------------
|
|
||||||
//
|
|
||||||
// ref/hare/getopt/getopts.ha:202-335.
|
// ref/hare/getopt/getopts.ha:202-335.
|
||||||
//
|
//
|
||||||
// Divergences from Hare (all forced):
|
// Divergences from Hare (all forced):
|
||||||
@@ -447,13 +445,11 @@ fn _printusage(
|
|||||||
) (size | io.error) = {
|
) (size | io.error) = {
|
||||||
let z: size = 0;
|
let z: size = 0;
|
||||||
|
|
||||||
// "Usage: <name>"
|
|
||||||
match (fmt.fprint(out, "Usage:", name)) {
|
match (fmt.fprint(out, "Usage:", name)) {
|
||||||
case let n: size => z += n;
|
case let n: size => z += n;
|
||||||
case let e: io.error => return e;
|
case let e: io.error => return e;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optional auto-[-h] + flag cluster [-Xabc].
|
|
||||||
let startedflags: bool = false;
|
let startedflags: bool = false;
|
||||||
if (!hascmdh) {
|
if (!hascmdh) {
|
||||||
match (fmt.fprint(out, " [-h")) {
|
match (fmt.fprint(out, " [-h")) {
|
||||||
@@ -485,7 +481,6 @@ fn _printusage(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parameter slots [-X <name>].
|
|
||||||
i = 0;
|
i = 0;
|
||||||
for (i < help.len) {
|
for (i < help.len) {
|
||||||
let p: *help = &help[i];
|
let p: *help = &help[i];
|
||||||
@@ -571,7 +566,6 @@ export fn printhelp(
|
|||||||
return void;
|
return void;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Print "name: summary\n\n" if help[0] is a CMD entry.
|
|
||||||
let p0: *help = &help[0];
|
let p0: *help = &help[0];
|
||||||
if (p0.kind == helpkind.CMD) {
|
if (p0.kind == helpkind.CMD) {
|
||||||
match (fmt.fprintfln(out, "{}: {}\n", name, p0.text)) {
|
match (fmt.fprintfln(out, "{}: {}\n", name, p0.text)) {
|
||||||
@@ -593,13 +587,11 @@ export fn printhelp(
|
|||||||
case let e: io.error => return e;
|
case let e: io.error => return e;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Blank line between usage and option list.
|
|
||||||
match (fmt.fprint(out, "\n")) {
|
match (fmt.fprint(out, "\n")) {
|
||||||
case let n: size => {};
|
case let n: size => {};
|
||||||
case let e: io.error => return e;
|
case let e: io.error => return e;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto -h line if not declared.
|
|
||||||
if (!hascmdh) {
|
if (!hascmdh) {
|
||||||
match (fmt.fprintln(out, "-h: print this help text")) {
|
match (fmt.fprintln(out, "-h: print this help text")) {
|
||||||
case let n: size => {};
|
case let n: size => {};
|
||||||
@@ -607,7 +599,6 @@ export fn printhelp(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Per-option lines.
|
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < help.len) {
|
for (i < help.len) {
|
||||||
let p: *help = &help[i];
|
let p: *help = &help[i];
|
||||||
|
|||||||
@@ -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
|
// Every @test enumerates parallel `[N]T` arrays of inputs and
|
||||||
// expectations, then iterates one body across them. Parallel arrays
|
// expectations, then iterates one body across them. Parallel arrays
|
||||||
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
|
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
|
||||||
@@ -36,8 +33,6 @@ fn streq(a: str, b: str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- flagcluster: -Fahs files.txt → 4 flags + 1 arg --------------------
|
|
||||||
|
|
||||||
@test fn flagcluster() void = {
|
@test fn flagcluster() void = {
|
||||||
let helps: [5]getopt.help;
|
let helps: [5]getopt.help;
|
||||||
getopt.cmdhelp(&helps[0], "list files");
|
getopt.cmdhelp(&helps[0], "list files");
|
||||||
@@ -82,8 +77,6 @@ fn streq(a: str, b: str) bool = {
|
|||||||
getopt.finish(&cmd);
|
getopt.finish(&cmd);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- paramflag: glued + separated arguments ----------------------------
|
|
||||||
|
|
||||||
@test fn paramflag() void = {
|
@test fn paramflag() void = {
|
||||||
let helps: [3]getopt.help;
|
let helps: [3]getopt.help;
|
||||||
getopt.cmdhelp(&helps[0], "edit");
|
getopt.cmdhelp(&helps[0], "edit");
|
||||||
@@ -125,8 +118,6 @@ fn streq(a: str, b: str) bool = {
|
|||||||
getopt.finish(&cmd);
|
getopt.finish(&cmd);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- separator: -- ends option processing ------------------------------
|
|
||||||
//
|
|
||||||
// `--` in three positions: after a flag, immediately after argv[0],
|
// `--` in three positions: after a flag, immediately after argv[0],
|
||||||
// and at the tail with nothing trailing. Flat-packed argv per
|
// and at the tail with nothing trailing. Flat-packed argv per
|
||||||
// errortable's pattern; rows expect distinct (optslen, argslen,
|
// 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 = {
|
@test fn repeatedflag() void = {
|
||||||
let helps: [2]getopt.help;
|
let helps: [2]getopt.help;
|
||||||
getopt.cmdhelp(&helps[0], "verbose count");
|
getopt.cmdhelp(&helps[0], "verbose count");
|
||||||
@@ -207,8 +196,6 @@ fn streq(a: str, b: str) bool = {
|
|||||||
getopt.finish(&cmd);
|
getopt.finish(&cmd);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- baredash: "-" alone is a positional --------------------------------
|
|
||||||
//
|
|
||||||
// Bare `-` in three positions: trailing after a flag, leading (which
|
// Bare `-` in three positions: trailing after a flag, leading (which
|
||||||
// halts option scanning), and followed by what looks like a flag
|
// halts option scanning), and followed by what looks like a flag
|
||||||
// (also halts — first non-flag wins).
|
// (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`
|
// Parallel arrays per row: argv flat-packed into `srcs`, with `argo`
|
||||||
// the offset and `argn` the count for each row.
|
// the offset and `argn` the count for each row.
|
||||||
@test fn errortable() void = {
|
@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`
|
// Parallel rows over (kind, flag, name) → expected message. `kind`
|
||||||
// rides as i32 because the errorkind enum doesn't yet index an
|
// rides as i32 because the errorkind enum doesn't yet index an
|
||||||
// array element directly.
|
// 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-only argv at three lengths: two positionals, one
|
||||||
// positional, and the program-name-only edge.
|
// positional, and the program-name-only edge.
|
||||||
@test fn nooptionsbare() void = {
|
@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.
|
// ref/hare/getopt/getopts.ha:202-265.
|
||||||
// Row 0: short line (≤72) — auto-[-h] + FLAG cluster, no wrap.
|
// Row 0: short line (≤72) — auto-[-h] + FLAG cluster, no wrap.
|
||||||
// Row 1: long line (>72) — PARAM slots each prefixed with \n\t.
|
// 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)`.
|
// tryparse/finish free the parsed-option array at `cap * size(option)`.
|
||||||
// option = rune (4) + pad (4) + str (24, #1/Phase 3) = 32B. A hardcoded
|
// 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
|
// `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));
|
assert(!(size(getopt.option) != 32));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- freeroundtrip: parse → finish over {0, 1, several} options --------
|
|
||||||
//
|
|
||||||
// Exercises the free path at three distinct cap values so the
|
// Exercises the free path at three distinct cap values so the
|
||||||
// `cap * size(option)` free in [[finish]] runs for an empty, single,
|
// `cap * size(option)` free in [[finish]] runs for an empty, single,
|
||||||
// and multi-element option array. The under-free is not directly
|
// 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.
|
// ref/hare/getopt/getopts.ha:281-314.
|
||||||
// Row 0: empty help slice → early return, no output.
|
// Row 0: empty help slice → early return, no output.
|
||||||
// Row 1: full help (CMD+FLAG+PARAM+CMD) → summary + usage + option list.
|
// Row 1: full help (CMD+FLAG+PARAM+CMD) → summary + usage + option list.
|
||||||
|
|||||||
@@ -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;
|
// 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.
|
// we ship the pure-buffer subset here, same shape as lib/hash/fnv.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// hash/crc16 — CRC-16 checksum. Pure ww.
|
|
||||||
//
|
|
||||||
// Inline polynomial-shift per byte (no precomputed tables). Slower
|
// Inline polynomial-shift per byte (no precomputed tables). Slower
|
||||||
// than a table-driven CRC by ~8x per byte but matches the
|
// than a table-driven CRC by ~8x per byte but matches the
|
||||||
// table-driven answer bit-for-bit.
|
// 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
|
// sum16 — fold `buf` under `poly` and return ~cval. Initial value is
|
||||||
// ~0u16, matching the streaming CRC-16 contract for a single
|
// ~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
|
// write-then-sum.
|
||||||
// an 8-bit index, run 8 polynomial shifts on that index, XOR the
|
|
||||||
// result with the high byte of cval shifted down.
|
|
||||||
export fn sum16(buf: []u8, poly: u16) u16 = {
|
export fn sum16(buf: []u8, poly: u16) u16 = {
|
||||||
let c: u16 = 0xFFFFu16;
|
let c: u16 = 0xFFFFu16;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// hash/crc32 — CRC-32 checksum. Pure ww.
|
|
||||||
//
|
|
||||||
// Same shape as lib/hash/crc16: per-byte inline polynomial shift,
|
// Same shape as lib/hash/crc16: per-byte inline polynomial shift,
|
||||||
// no precomputed table. Slower than Hare's table-driven path by ~8x
|
// no precomputed table. Slower than Hare's table-driven path by ~8x
|
||||||
// per byte but produces identical answers.
|
// 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 CASTAGNOLI: u32 = 0x82F63B78u32; // iSCSI, SCTP, SSE4.2
|
||||||
def KOOPMAN: u32 = 0xEB31D82Eu32; // small datasets
|
def KOOPMAN: u32 = 0xEB31D82Eu32; // small datasets
|
||||||
|
|
||||||
// sum32 — fold `buf` under `poly` (reversed form). Initial cval is
|
// sum32 — fold `buf` under `poly` (reversed form). Initial cval is ~0u32.
|
||||||
// ~0u32; per byte we mix in the low byte via 8 polynomial shifts and
|
|
||||||
// XOR with the high three bytes shifted down.
|
|
||||||
export fn sum32(buf: []u8, poly: u32) u32 = {
|
export fn sum32(buf: []u8, poly: u32) u32 = {
|
||||||
let c: u32 = 0xFFFFFFFFu32;
|
let c: u32 = 0xFFFFFFFFu32;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// hash/crc64 — CRC-64 checksum. Pure ww.
|
|
||||||
//
|
|
||||||
// Same shape as lib/hash/crc32: per-byte inline polynomial shift, no
|
// Same shape as lib/hash/crc32: per-byte inline polynomial shift, no
|
||||||
// precomputed table. Slower than Hare's table-driven path by ~8x per
|
// precomputed table. Slower than Hare's table-driven path by ~8x per
|
||||||
// byte but produces identical answers for the documented polynomials.
|
// byte but produces identical answers for the documented polynomials.
|
||||||
@@ -11,9 +9,7 @@ package crc64;
|
|||||||
def ECMA: u64 = 0xC96C5795D7870F42u64; // ECMA-182, xz-utils
|
def ECMA: u64 = 0xC96C5795D7870F42u64; // ECMA-182, xz-utils
|
||||||
def ISO: u64 = 0xD800000000000000u64; // ISO 3309 HDLC
|
def ISO: u64 = 0xD800000000000000u64; // ISO 3309 HDLC
|
||||||
|
|
||||||
// sum64 — fold `buf` under `poly` (reversed form). Initial cval is
|
// sum64 — fold `buf` under `poly` (reversed form). Initial cval is ~0u64.
|
||||||
// ~0u64; per byte we mix in the low byte via 8 polynomial shifts and
|
|
||||||
// XOR with the high seven bytes shifted down.
|
|
||||||
export fn sum64(buf: []u8, poly: u64) u64 = {
|
export fn sum64(buf: []u8, poly: u64) u64 = {
|
||||||
let c: u64 = 0xFFFFFFFFFFFFFFFFu64;
|
let c: u64 = 0xFFFFFFFFFFFFFFFFu64;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// hash/fnv — FNV-1a 64-bit. Pure ww. No dependencies.
|
|
||||||
|
|
||||||
package fnv;
|
package fnv;
|
||||||
|
|
||||||
def OFFSET: u64 = 14695981039346656037;
|
def OFFSET: u64 = 14695981039346656037;
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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
|
// io::stream-backed type; ww's subset doesn't, matching the rest of
|
||||||
|
|||||||
10
lib/io/io.ww
10
lib/io/io.ww
@@ -5,19 +5,13 @@
|
|||||||
// through it. The error channel is the return value — Hare-shaped
|
// through it. The error channel is the return value — Hare-shaped
|
||||||
// tagged unions instead of errno-style integer sentinels.
|
// 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`
|
// #94 fold-eFinal collapsed the pre-vtable `stream` struct + `closed`
|
||||||
// tag into the single vtable surface; the dispatchers are read/write/
|
// tag into the single vtable surface; the dispatchers are read/write/
|
||||||
// close (over `stream`), final over `handle` at io fold-2 (#5).
|
// close (over `stream`), final over `handle` at io fold-2 (#5).
|
||||||
package io;
|
package io;
|
||||||
|
|
||||||
// eof — read past the end of the stream. Hare uses the `done`
|
// Hare uses the `done` singleton for EOF; ww doesn't have `done` yet
|
||||||
// singleton for EOF; ww doesn't have `done` yet so we ship a
|
// so we ship a named-void variant tag. Lifts to `done` with #93.
|
||||||
// named-void variant tag. Lifts to `done` with #93.
|
|
||||||
export type eof = void;
|
export type eof = void;
|
||||||
|
|
||||||
// underread — an I/O handle hit eof partway through a fixed-size
|
// underread — an I/O handle hit eof partway through a fixed-size
|
||||||
|
|||||||
@@ -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
|
// ref/hare/io/stream.ha:70-77.
|
||||||
// over the new `seeker` vtable slot.
|
|
||||||
fn st_seek(s: stream, off: off, w: whence) (off | error) = {
|
fn st_seek(s: stream, off: off, w: whence) (off | error) = {
|
||||||
match (s.seeker) {
|
match (s.seeker) {
|
||||||
case void => {
|
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
|
// ref/hare/io/handle.ha:16-23. The file arm routes to os.read
|
||||||
// file-arm routes to os.read (Q2 layering), the stream-arm delegates to
|
// directly (the Q2 layering ruling: os stays below io).
|
||||||
// the unchanged st_read vtable dispatch above.
|
|
||||||
export fn read(h: handle, buf: []u8) (size | eof | error) = {
|
export fn read(h: handle, buf: []u8) (size | eof | error) = {
|
||||||
match (h) {
|
match (h) {
|
||||||
case let fd: file => {
|
case let fd: 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
|
// 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;
|
// the same (#94 fold-eFinal). The four files share `package io;` so
|
||||||
// lib/io/stream.ww owns the vtable surface (`vtable`, `stream`,
|
// cross-file refs resolve via the dir-enum concat order (io.ww <
|
||||||
// `read`/`write`/`close` dispatch); this file owns the surrounding
|
// stream.ww < types.ww — `stream` lands before the reader/writer/
|
||||||
// port. The four files share `package io;` so cross-file refs
|
// closer aliases below).
|
||||||
// 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
|
// Deferrals (drew-signed): `copier`, `strerror` (the #5 list's
|
||||||
// `handle` + `seeker` landed with the #5 arc and the memio-seeker
|
// `handle` + `seeker` landed with the #5 arc and the memio-seeker
|
||||||
|
|||||||
@@ -2,24 +2,6 @@
|
|||||||
// Subset of Hare's lib/log (ref/hare/log/{logger,funcs,global,silent}.ha).
|
// Subset of Hare's lib/log (ref/hare/log/{logger,funcs,global,silent}.ha).
|
||||||
// Project #94 fold-eFinal.
|
// 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
|
// 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 in a local and `return r;` (proven field-by-field sret).
|
||||||
// stdlogger is 24B (two fn-ptrs + sink), returned via the SysV memory
|
// 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 default: *logger;
|
||||||
export let global: *logger;
|
export let global: *logger;
|
||||||
|
|
||||||
// initdone — guards [[ensureinit]] so the one-shot wiring runs once.
|
|
||||||
let initdone: i32 = 0;
|
let initdone: i32 = 0;
|
||||||
|
|
||||||
fn ensureinit() void = {
|
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 silentprintln(l: *logger, args: fmt.formattable...) void = { };
|
||||||
fn silentprintfln(l: *logger, format: str, fields: fmt.field...) void = { };
|
fn silentprintfln(l: *logger, format: str, fields: fmt.field...) void = { };
|
||||||
|
|
||||||
// ---- public API ----------------------------------------------------------
|
|
||||||
|
|
||||||
// new — build a stdlogger over `sink`, returned BY VALUE. Mirrors
|
// new — build a stdlogger over `sink`, returned BY VALUE. Mirrors
|
||||||
// ref/hare/log/logger.ha:20.
|
// ref/hare/log/logger.ha:20.
|
||||||
export fn new(sink: io.stream) stdlogger = {
|
export fn new(sink: io.stream) stdlogger = {
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ import memio;
|
|||||||
assert(!(mem.pos != 0));
|
assert(!(mem.pos != 0));
|
||||||
};
|
};
|
||||||
|
|
||||||
// The silent logger's printfln callback discards args without touching
|
// Same pattern as [[silentwritesnothing]] for the bare-args path.
|
||||||
// fmt or any sink. Same pattern as [[silentwritesnothing]] for the
|
|
||||||
// bare-args path.
|
|
||||||
@test fn silentignoresprintfln() void = {
|
@test fn silentignoresprintfln() void = {
|
||||||
let buf: [16]u8;
|
let buf: [16]u8;
|
||||||
let mem = memio.fixed(buf[0:16]);
|
let mem = memio.fixed(buf[0:16]);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// math/checked — overflow-checked integer arithmetic. Ported from
|
// Ported from Hare's math::checked (ref/hare/math/checked/checked.ha).
|
||||||
// Hare's math::checked (ref/hare/math/checked/checked.ha). add*/sub*/
|
// add*/sub*/mul* return (result, overflow) with wrapping semantics. The
|
||||||
// mul* return (result, overflow) with wrapping semantics. The saturating
|
// saturating (clamp-on-overflow) siblings live in saturating.ww — same
|
||||||
// (clamp-on-overflow) siblings live in saturating.ww — same `checked`
|
// `checked` module.
|
||||||
// module.
|
|
||||||
//
|
//
|
||||||
// Subset of Hare's surface (Hare splits per type, so these are clean
|
// Subset of Hare's surface (Hare splits per type, so these are clean
|
||||||
// omissions, not divergences):
|
// omissions, not divergences):
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// math/checked (saturating) — clamp-on-overflow arithmetic. Ported from
|
// Ported from Hare's math::checked saturating siblings
|
||||||
// Hare's math::checked saturating siblings
|
|
||||||
// (ref/hare/math/checked/saturating.ha). Part of the same `checked`
|
// (ref/hare/math/checked/saturating.ha). Part of the same `checked`
|
||||||
// module as checked.ww. Each sat_* clamps to the type's range on
|
// module as checked.ww. Each sat_* clamps to the type's range on
|
||||||
// overflow instead of wrapping.
|
// overflow instead of wrapping.
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// floats — f64 classification, sign, bit-reinterpret core, and the f64
|
// Ported from ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
|
||||||
// decompose half (subnormal-normalize + frexp). Ported from
|
|
||||||
// ref/hare/math/floats.ha (fold-1: classify/sign/bits; fold-2a:
|
|
||||||
// issubnormalf64/normalizef64/frexpf64; strconv-foundation fold-1a:
|
// issubnormalf64/normalizef64/frexpf64; strconv-foundation fold-1a:
|
||||||
// F32 bit-layout + f32bits/f32frombits + floatinfo struct type;
|
// F32 bit-layout + f32bits/f32frombits + floatinfo struct type;
|
||||||
// fold-1b: NAN_BITS/INF_BITS sentinels; γ-cleanup: f64info/f32info
|
// fold-1b: NAN_BITS/INF_BITS sentinels; γ-cleanup: f64info/f32info
|
||||||
@@ -12,7 +10,6 @@
|
|||||||
|
|
||||||
package math;
|
package math;
|
||||||
|
|
||||||
// Returns the binary representation of the given f64.
|
|
||||||
// ref/hare/math/floats.ha:5. Parens around &n are load-bearing: ww's `:`
|
// 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
|
// cast binds tighter than unary `&`, so Hare's `*(&n: *u64)` would parse
|
||||||
// as `*(&(n: *u64))`; `(&n): *u64` reinterprets the address as intended.
|
// as `*(&(n: *u64))`; `(&n): *u64` reinterprets the address as intended.
|
||||||
@@ -20,19 +17,16 @@ export fn f64bits(n: f64) u64 = {
|
|||||||
return *((&n): *u64);
|
return *((&n): *u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the binary representation of the given f32.
|
|
||||||
// ref/hare/math/floats.ha:8
|
// ref/hare/math/floats.ha:8
|
||||||
export fn f32bits(n: f32) u32 = {
|
export fn f32bits(n: f32) u32 = {
|
||||||
return *((&n): *u32);
|
return *((&n): *u32);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns f64 with the given binary representation.
|
|
||||||
// ref/hare/math/floats.ha:11
|
// ref/hare/math/floats.ha:11
|
||||||
export fn f64frombits(n: u64) f64 = {
|
export fn f64frombits(n: u64) f64 = {
|
||||||
return *((&n): *f64);
|
return *((&n): *f64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns f32 with the given binary representation.
|
|
||||||
// ref/hare/math/floats.ha:14
|
// ref/hare/math/floats.ha:14
|
||||||
export fn f32frombits(n: u32) f32 = {
|
export fn f32frombits(n: u32) f32 = {
|
||||||
return *((&n): *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
|
// amounts and mask widths, so they are typed u64 here — the closest
|
||||||
// stand-in for Hare's untyped-int adapt at those use sites.
|
// 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;
|
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;
|
export def F64_EXPONENT_BITS: u64 = 11;
|
||||||
|
|
||||||
// The bias of the exponent of the binary representation of f64. Subtract this
|
// 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.
|
// from the exponent in the binary representation to get the actual exponent.
|
||||||
export def F64_EXPONENT_BIAS: u64 = 1023;
|
export def F64_EXPONENT_BIAS: u64 = 1023;
|
||||||
|
|
||||||
// Mask with each bit of an f64's mantissa set.
|
|
||||||
// ref/hare/math/floats.ha:37
|
// ref/hare/math/floats.ha:37
|
||||||
export def F64_MANTISSA_MASK: u64 = (1 << F64_MANTISSA_BITS) - 1;
|
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
|
// ref/hare/math/floats.ha:40
|
||||||
export def F64_EXPONENT_MASK: u64 = (1 << F64_EXPONENT_BITS) - 1;
|
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
|
// ref/hare/math/floats.ha:75
|
||||||
def F64_SIGN_MASK: u64 = 1u64 << 63;
|
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 u32 bit container, the same way the F64 family rides u64 — see
|
||||||
// the note above F64_MANTISSA_BITS).
|
// 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
|
// ref/hare/math/floats.ha:27
|
||||||
export def F32_MANTISSA_BITS: u32 = 23u32;
|
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
|
// ref/hare/math/floats.ha:30
|
||||||
export def F32_EXPONENT_BITS: u32 = 8u32;
|
export def F32_EXPONENT_BITS: u32 = 8u32;
|
||||||
|
|
||||||
@@ -97,15 +84,12 @@ export def F32_EXPONENT_BITS: u32 = 8u32;
|
|||||||
// ref/hare/math/floats.ha:33
|
// ref/hare/math/floats.ha:33
|
||||||
export def F32_EXPONENT_BIAS: u32 = 127u32;
|
export def F32_EXPONENT_BIAS: u32 = 127u32;
|
||||||
|
|
||||||
// Mask with each bit of an f32's mantissa set.
|
|
||||||
// ref/hare/math/floats.ha:43
|
// ref/hare/math/floats.ha:43
|
||||||
export def F32_MANTISSA_MASK: u32 = (1u32 << F32_MANTISSA_BITS) - 1u32;
|
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
|
// ref/hare/math/floats.ha:46
|
||||||
export def F32_EXPONENT_MASK: u32 = (1u32 << F32_EXPONENT_BITS) - 1u32;
|
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
|
// ref/hare/math/floats.ha:87
|
||||||
def F32_SIGN_MASK: u32 = 1u32 << 31;
|
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 ref/hare/strconv/stof.ha:248,288 (`let e: int = 0` arithmetic
|
||||||
// against `f.expbias` of the same type, no cast at use site).
|
// against `f.expbias` of the same type, no cast at use site).
|
||||||
export type floatinfo = struct {
|
export type floatinfo = struct {
|
||||||
// Bits in significand.
|
|
||||||
mantbits: u64,
|
mantbits: u64,
|
||||||
// Bits in exponent.
|
|
||||||
expbits: u64,
|
expbits: u64,
|
||||||
// Bias of exponent.
|
|
||||||
expbias: int,
|
expbias: int,
|
||||||
// Mask for mantissa.
|
|
||||||
mantmask: u64,
|
mantmask: u64,
|
||||||
// Mask for exponent.
|
|
||||||
expmask: u64,
|
expmask: u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -177,14 +156,12 @@ export def f32info: floatinfo = floatinfo {
|
|||||||
export def NAN_BITS: u64 = 0x7FF8000000000000u64;
|
export def NAN_BITS: u64 = 0x7FF8000000000000u64;
|
||||||
export def INF_BITS: u64 = 0x7FF0000000000000u64;
|
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
|
// ref/hare/math/floats.ha:144 (Hare's expression body inlined into a
|
||||||
// block: ww has no expression-bodied fn form, only brace blocks).
|
// block: ww has no expression-bodied fn form, only brace blocks).
|
||||||
export fn isnan(n: f64) bool = {
|
export fn isnan(n: f64) bool = {
|
||||||
return n != n;
|
return n != n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns true if the given floating-point number is infinite.
|
|
||||||
// ref/hare/math/floats.ha:147
|
// ref/hare/math/floats.ha:147
|
||||||
export fn isinf(n: f64) bool = {
|
export fn isinf(n: f64) bool = {
|
||||||
const bits = f64bits(n);
|
const bits = f64bits(n);
|
||||||
@@ -193,7 +170,6 @@ export fn isinf(n: f64) bool = {
|
|||||||
return exp == F64_EXPONENT_MASK && mant == 0;
|
return exp == F64_EXPONENT_MASK && mant == 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns true if the given f64 is subnormal.
|
|
||||||
// ref/hare/math/floats.ha:179
|
// ref/hare/math/floats.ha:179
|
||||||
export fn issubnormalf64(n: f64) bool = {
|
export fn issubnormalf64(n: f64) bool = {
|
||||||
const bits = f64bits(n);
|
const bits = f64bits(n);
|
||||||
@@ -202,7 +178,6 @@ export fn issubnormalf64(n: f64) bool = {
|
|||||||
return exp == 0 && mant != 0;
|
return exp == 0 && mant != 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the absolute value of f64 n.
|
|
||||||
// ref/hare/math/floats.ha:195
|
// ref/hare/math/floats.ha:195
|
||||||
export fn absf64(n: f64) f64 = {
|
export fn absf64(n: f64) f64 = {
|
||||||
if (isnan(n)) {
|
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
|
// ref/hare/math/floats.ha:231
|
||||||
export fn ispositivef64(x: f64) bool = {
|
export fn ispositivef64(x: f64) bool = {
|
||||||
return signf64(x) == 1i64;
|
return signf64(x) == 1i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns whether or not x is negative.
|
|
||||||
// ref/hare/math/floats.ha:237
|
// ref/hare/math/floats.ha:237
|
||||||
export fn isnegativef64(x: f64) bool = {
|
export fn isnegativef64(x: f64) bool = {
|
||||||
return signf64(x) == -1i64;
|
return signf64(x) == -1i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns x, but with the sign of y.
|
|
||||||
// ref/hare/math/floats.ha:243
|
// ref/hare/math/floats.ha:243
|
||||||
export fn copysignf64(x: f64, y: f64) f64 = {
|
export fn copysignf64(x: f64, y: f64) f64 = {
|
||||||
return f64frombits((f64bits(x) & ~F64_SIGN_MASK) |
|
return f64frombits((f64bits(x) & ~F64_SIGN_MASK) |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// math — numeric helpers. Subset of Hare's math::; only the absolute-
|
// Subset of Hare's math::; only the absolute-value pair for the signed
|
||||||
// value pair for the signed integer types we currently care about. The
|
// integer types we currently care about. The return type is unsigned
|
||||||
// return type is unsigned so that abs(I32_MIN) doesn't overflow.
|
// so that abs(I32_MIN) doesn't overflow.
|
||||||
|
|
||||||
package math;
|
package math;
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ package random;
|
|||||||
|
|
||||||
export type random = u64;
|
export type random = u64;
|
||||||
|
|
||||||
// init — initialize a generator with `seed`. Same seed reproduces the
|
// Mirrors Hare's random::init.
|
||||||
// same sequence on every run. Mirrors Hare's random::init.
|
|
||||||
export fn init(seed: u64) random = { return seed: random; };
|
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.
|
// SplitMix64, per Hare's random::next.
|
||||||
export fn next(r: *random) u64 = {
|
export fn next(r: *random) u64 = {
|
||||||
let s: u64 = (*r): u64 + 0x9E3779B97F4A7C15u64;
|
let s: u64 = (*r): u64 + 0x9E3779B97F4A7C15u64;
|
||||||
|
|||||||
@@ -122,8 +122,6 @@ export fn dynamicfrom(buf: []u8) stream = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- vtable callbacks ----------------------------------------------------
|
|
||||||
|
|
||||||
// readfn — recover the stream from the io.stream's `*vtable` via the
|
// readfn — recover the stream from the io.stream's `*vtable` via the
|
||||||
// intrusive offset-0 cast. Single fn over the common header (Hare's
|
// intrusive offset-0 cast. Single fn over the common header (Hare's
|
||||||
// single `read` at ref/hare/memio/stream.ha:103); fixed and dynamic
|
// 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;
|
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]).
|
// string — bytes written so far, as a str view (buf[0..pos]).
|
||||||
//
|
//
|
||||||
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
|
// Mirrors ref/hare/memio/stream.ha:81 string(in: *stream). Hare returns
|
||||||
|
|||||||
@@ -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
|
// Every @test enumerates parallel `[N]T` arrays of inputs and
|
||||||
// expectations, then iterates one body across them. Parallel arrays
|
// expectations, then iterates one body across them. Parallel arrays
|
||||||
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
|
// (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;
|
return off + s.len;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fixedread: read sizes drive partial / full / eof outcomes ----------
|
|
||||||
|
|
||||||
@test fn fixedread() void = {
|
@test fn fixedread() void = {
|
||||||
let arr: [8]u8;
|
let arr: [8]u8;
|
||||||
arr[0] = 1u8; arr[1] = 2u8; arr[2] = 3u8; arr[3] = 4u8;
|
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(); };
|
match (c) { case void => {}; case let e: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fixedwritecases: full-fit / exact-fill / partial / overflow -------------
|
|
||||||
|
|
||||||
@test fn fixedwritecases() void = {
|
@test fn fixedwritecases() void = {
|
||||||
let dst: [16]u8;
|
let dst: [16]u8;
|
||||||
let st: memio.stream = memio.fixed(dst[0:16]);
|
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(); };
|
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
|
// A write that exactly fills the sink succeeds; any further non-empty
|
||||||
// write into the full sink returns nomem, not a 0-byte success
|
// 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
|
// (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
|
// Drive grow 0 → 8 → 16 → 32 by writing sized chunks. Verify
|
||||||
// accumulated `pos` after each step.
|
// accumulated `pos` after each step.
|
||||||
@test fn dynamicgrowcases() void = {
|
@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(); };
|
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
|
// op=0 writes `ln` bytes from a rolling source; op=1 resets and ignores
|
||||||
// ln. After each row the accumulated len must equal `want`.
|
// ln. After each row the accumulated len must equal `want`.
|
||||||
@test fn dynamicreset() void = {
|
@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(); };
|
match (c) { case void => {}; case let e: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- borrowedread: under / exact / over / 0-byte ------------------------
|
|
||||||
|
|
||||||
@test fn borrowedreadcases() void = {
|
@test fn borrowedreadcases() void = {
|
||||||
let arr: [6]u8;
|
let arr: [6]u8;
|
||||||
arr[0]=0u8; arr[1]=1u8; arr[2]=2u8; arr[3]=3u8; arr[4]=4u8; arr[5]=5u8;
|
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 = {
|
@test fn stringview() void = {
|
||||||
let st: memio.stream = memio.dynamic();
|
let st: memio.stream = memio.dynamic();
|
||||||
let s: io.stream = &st.vt;
|
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(); };
|
match (c) { case void => {}; case let e: io.error => abort(); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- dynamicfromseed: ownership-transferred seed, read then write -------
|
|
||||||
|
|
||||||
@test fn dynamicfromseed() void = {
|
@test fn dynamicfromseed() void = {
|
||||||
// Build a heap-allocated seed via append (rt_ensure path), then
|
// Build a heap-allocated seed via append (rt_ensure path), then
|
||||||
// hand ownership to memio. dynamicclose then frees `seed.cap`
|
// 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(); };
|
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
|
// Sequential rows over one 8-byte fixed stream; CUR rows depend on the
|
||||||
// cursor left by earlier rows. want = resulting pos for valid rows,
|
// cursor left by earlier rows. want = resulting pos for valid rows,
|
||||||
// -1 = errors.invalid expected. After EVERY row an io.tell readback
|
// -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 = {
|
@test fn emptyseek() void = {
|
||||||
let arr: [1]u8;
|
let arr: [1]u8;
|
||||||
arr[0] = 0u8;
|
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
|
// The dynamic vtable wires the same seekfn; bounds run against the
|
||||||
// LOGICAL length (m.len), not capacity (Hare: len(s.buf),
|
// LOGICAL length (m.len), not capacity (Hare: len(s.buf),
|
||||||
// ref/hare/memio/stream.ha:131,136).
|
// ref/hare/memio/stream.ha:131,136).
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// net — minimal TCP. Sketched against the Linux syscall numbers
|
// net — minimal TCP. Real applications will want addrinfo + DNS; we
|
||||||
// 41 (socket), 42 (connect), 43 (accept), 49 (bind), 50 (listen).
|
// leave that to higher layers.
|
||||||
// Real applications will want addrinfo + DNS; we leave that to
|
|
||||||
// higher layers.
|
|
||||||
|
|
||||||
package net;
|
package net;
|
||||||
|
|
||||||
|
|||||||
34
lib/os/os.ww
34
lib/os/os.ww
@@ -1,6 +1,5 @@
|
|||||||
// os — process and filesystem facade. The body of each call lands
|
// The body of each call lands either in libwwrt.a (rt_syscall
|
||||||
// either in libwwrt.a (rt_syscall trampoline) or libc bindings,
|
// trampoline) or libc bindings, depending on how the program was linked.
|
||||||
// depending on how the program was linked.
|
|
||||||
|
|
||||||
package os;
|
package os;
|
||||||
|
|
||||||
@@ -155,10 +154,8 @@ export fn close(fd: i32) i32 = {
|
|||||||
return syscall1(nr.CLOSE, fd: i64): i32;
|
return syscall1(nr.CLOSE, fd: i64): i32;
|
||||||
};
|
};
|
||||||
|
|
||||||
// dup2(2): make `newfd` refer to the same description as `oldfd`,
|
// Used by w6c_ww to redirect stdout into an output file without
|
||||||
// closing `newfd` first if open. Returns `newfd` on success or a
|
// changing the cgen emit path.
|
||||||
// negative errno. 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 = {
|
export fn dup2(oldfd: i32, newfd: i32) i32 = {
|
||||||
return syscall2(nr.DUP2, oldfd: i64, newfd: i64): 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;
|
return fd;
|
||||||
};
|
};
|
||||||
|
|
||||||
// lseek — set/inspect the fd's position. Returns the new offset or
|
// Used for fstat-free file-size discovery (open ⇒ lseek to end ⇒
|
||||||
// a negative errno. We use this for fstat-free file-size discovery
|
// lseek back).
|
||||||
// (open ⇒ lseek to end ⇒ lseek back).
|
|
||||||
export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
export fn lseek(fd: i32, off: i64, w: whence) i64 = {
|
||||||
return syscall3(nr.LSEEK, fd: i64, off, (w as i32): 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";
|
return "Unknown error";
|
||||||
};
|
};
|
||||||
|
|
||||||
// filesize — byte length of an open fd via lseek-to-end-and-back.
|
|
||||||
export fn filesize(fd: i32) (i64 | oserror) = {
|
export fn filesize(fd: i32) (i64 | oserror) = {
|
||||||
let end: i64 = lseek(fd, 0i64, whence.END);
|
let end: i64 = lseek(fd, 0i64, whence.END);
|
||||||
if (end < 0) { return end: oserror; };
|
if (end < 0) { return end: oserror; };
|
||||||
@@ -282,9 +277,8 @@ export fn filesize(fd: i32) (i64 | oserror) = {
|
|||||||
return end;
|
return end;
|
||||||
};
|
};
|
||||||
|
|
||||||
// readall — keep reading until `n` bytes have arrived or the fd
|
// Hare name (io::readall); the buffer is caller-supplied, matching
|
||||||
// closes early. Hare name (io::readall); the buffer is caller-
|
// the Plan 9 subset convention.
|
||||||
// supplied, matching the Plan 9 subset convention.
|
|
||||||
export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let got: u64 = 0u64;
|
let got: u64 = 0u64;
|
||||||
for (got < n) {
|
for (got < n) {
|
||||||
@@ -296,8 +290,7 @@ export fn readall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
|||||||
return got: i64;
|
return got: i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// writeall — keep writing until `n` bytes have been accepted or the
|
// Hare name (io::writeall).
|
||||||
// fd refuses progress. Hare name (io::writeall).
|
|
||||||
export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
||||||
let sent: u64 = 0u64;
|
let sent: u64 = 0u64;
|
||||||
for (sent < n) {
|
for (sent < n) {
|
||||||
@@ -309,8 +302,6 @@ export fn writeall(fd: i32, buf: *u8, n: u64) (i64 | oserror) = {
|
|||||||
return sent: i64;
|
return sent: i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- process and filesystem helpers used by the `ww` driver ----------
|
|
||||||
|
|
||||||
// access(2): returns 0 if the file is reachable, negative errno
|
// access(2): returns 0 if the file is reachable, negative errno
|
||||||
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
// otherwise. mode is the bitset described in <unistd.h> (F_OK=0).
|
||||||
// Mirrors Hare's os::access (ref/hare/os/+linux/fs.ha:access).
|
// 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);
|
return syscall3(nr.GETDENTS64, fd: i64, buf: i64, n: i64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- environment ------------------------------------------------------
|
|
||||||
|
|
||||||
// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW
|
// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW
|
||||||
// slot before calling main; this binding lifts the captured pointer
|
// slot before calling main; this binding lifts the captured pointer
|
||||||
// into ww. Same FFI shape as rt_syscall / rt_malloc / rt_abort: a TEXT
|
// into ww. Same FFI shape as rt_syscall / rt_malloc / rt_abort: a TEXT
|
||||||
@@ -688,8 +677,6 @@ export fn args() []str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- stat / lstat / fstat / exists -----------------------------------
|
|
||||||
//
|
|
||||||
// Ports of Hare's stat family (ref/hare/fs/fs.ha:172,196 +
|
// 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
|
// 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
|
// `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];
|
let emptypath: [1]u8 = [0u8];
|
||||||
|
|
||||||
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
// fillfilestat — copy a 144B kstat into the 80B Hare-shaped
|
||||||
// filestat. Internal helper used by all three public entry points.
|
// filestat. Mirrors Hare's st_to_filestat (ref/hare/os/+linux/dirfdfs.ha:259):
|
||||||
// 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
|
// newfstatat populates every field, so the mask is the OR-fold of
|
||||||
// all seven Hare stat_mask bits.
|
// all seven Hare stat_mask bits.
|
||||||
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
fn fillfilestat(out: *filestat, k: *kstat) void = {
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// ostest — exercises lib/os surface that doesn't have a dedicated
|
// memio's tests indirectly cover alloc/free; the direct row here pins
|
||||||
// test elsewhere. Covers [[os.getenv]] and a direct
|
// the FFI shape under lib/os itself so future bindings refactors can't
|
||||||
// [[os.alloc]] / [[os.free]] roundtrip. memio's tests indirectly
|
// quietly drift.
|
||||||
// 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):
|
// getenv env contract (ww ships no setenv primitive, deliberately):
|
||||||
//
|
//
|
||||||
@@ -45,8 +43,6 @@ fn ostestwait(pid: i32, status: *i32) i32 = {
|
|||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- getenv: set var → matching value -------------------------------
|
|
||||||
|
|
||||||
@test fn test_getenv_set() void = {
|
@test fn test_getenv_set() void = {
|
||||||
let r = os.getenv("WW_TEST_GETENV");
|
let r = os.getenv("WW_TEST_GETENV");
|
||||||
match (r) {
|
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).
|
// POSIX permits an env var with an empty value (`name=` in environ).
|
||||||
// getenv must return the empty str, NOT void — void is reserved for
|
// 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 = {
|
@test fn test_getenv_empty() void = {
|
||||||
let r = os.getenv("WW_TEST_EMPTY");
|
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 = {
|
@test fn test_getenv_unset() void = {
|
||||||
let r = os.getenv("WW_TEST_NOT_SET");
|
let r = os.getenv("WW_TEST_NOT_SET");
|
||||||
match (r) {
|
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
|
// Probes that "WW_TEST_GETEN" (a prefix of WW_TEST_GETENV) doesn't
|
||||||
// match. Without the explicit `entry[name.len] == '='` check in
|
// match. Without the explicit `entry[name.len] == '='` check in
|
||||||
// getenv, a naive prefix matcher would return the value of any
|
// 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
|
// getenvs() is public API (task #28) and the single env walker getenv
|
||||||
// routes through. getenv's cases exercise the walk transitively, but
|
// routes through. getenv's cases exercise the walk transitively, but
|
||||||
// none asserts the []str RETURN shape — its count or "NAME=VALUE" entry
|
// 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
|
// Direct round-trip. Write-then-read-back proves the returned page is
|
||||||
// dereferenceable. A miscompiled binding (wrong arg order, wrong ABI,
|
// dereferenceable. A miscompiled binding (wrong arg order, wrong ABI,
|
||||||
// etc.) would either fault or return zero here.
|
// etc.) would either fault or return zero here.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// stattest — exercises [[os.stat]] / [[os.lstat]] / [[os.fstat]] /
|
// Each row arranges its own scratch tree under [[temp.dir]] (Hare's
|
||||||
// [[os.exists]] against a scratch tree each row arranges itself
|
// own idiom: os tests self-arrange in a temp dir), so the suite
|
||||||
// under [[temp.dir]] (Hare's own idiom: os tests self-arrange in a
|
// passes bare with no external driver:
|
||||||
// temp dir), so the suite passes bare with no external driver:
|
|
||||||
//
|
//
|
||||||
// <root>/regfile regular file, 11 bytes "hello world", 0644
|
// <root>/regfile regular file, 11 bytes "hello world", 0644
|
||||||
// <root>/symlink → ./regfile (relative symlink)
|
// <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);
|
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 = {
|
@test fn test_stat_regfile() void = {
|
||||||
let t: tree;
|
let t: tree;
|
||||||
mktree(&t);
|
mktree(&t);
|
||||||
@@ -112,8 +103,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
|||||||
rmtree(&t);
|
rmtree(&t);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- stat: directory ------------------------------------------------
|
|
||||||
|
|
||||||
@test fn test_stat_subdir() void = {
|
@test fn test_stat_subdir() void = {
|
||||||
let t: tree;
|
let t: tree;
|
||||||
mktree(&t);
|
mktree(&t);
|
||||||
@@ -127,8 +116,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
|||||||
rmtree(&t);
|
rmtree(&t);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- stat: missing path → oserror ENOENT ---------------------------
|
|
||||||
|
|
||||||
@test fn test_stat_noent() void = {
|
@test fn test_stat_noent() void = {
|
||||||
let t: tree;
|
let t: tree;
|
||||||
mktree(&t);
|
mktree(&t);
|
||||||
@@ -143,11 +130,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
|||||||
rmtree(&t);
|
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 = {
|
@test fn test_stat_symlink_follow() void = {
|
||||||
let t: tree;
|
let t: tree;
|
||||||
mktree(&t);
|
mktree(&t);
|
||||||
@@ -175,8 +157,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
|||||||
rmtree(&t);
|
rmtree(&t);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- fstat: open a file and stat by fd ------------------------------
|
|
||||||
|
|
||||||
@test fn test_fstat_regfile() void = {
|
@test fn test_fstat_regfile() void = {
|
||||||
let t: tree;
|
let t: tree;
|
||||||
mktree(&t);
|
mktree(&t);
|
||||||
@@ -194,8 +174,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
|||||||
rmtree(&t);
|
rmtree(&t);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- exists: true on regfile/dir/symlink, false on noent ------------
|
|
||||||
|
|
||||||
@test fn test_exists_regfile() void = {
|
@test fn test_exists_regfile() void = {
|
||||||
let t: tree;
|
let t: tree;
|
||||||
mktree(&t);
|
mktree(&t);
|
||||||
@@ -217,8 +195,6 @@ fn istype(m: os.mode, t: os.mode) bool = {
|
|||||||
rmtree(&t);
|
rmtree(&t);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- ENAMETOOLONG: kpath rejects paths >= PATH_MAX -------------------
|
|
||||||
//
|
|
||||||
// kpath copies into a single [PATH_MAX]u8 buffer and reserves one byte
|
// kpath copies into a single [PATH_MAX]u8 buffer and reserves one byte
|
||||||
// for the NUL terminator (`p.len + 1 >= PATH_MAX` → reject). The
|
// for the NUL terminator (`p.len + 1 >= PATH_MAX` → reject). The
|
||||||
// rejection surfaces as `oserror = -36` (ENAMETOOLONG) on (... |
|
// rejection surfaces as `oserror = -36` (ENAMETOOLONG) on (... |
|
||||||
|
|||||||
@@ -152,8 +152,6 @@ export fn set(buf: *buffer, items: str...) (str | error) = {
|
|||||||
return push(buf, items...);
|
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,
|
// lifts Hare's fn-`static let buf` (buffer.ha:53-56): statically allocated,
|
||||||
// overwritten on the next local() call. strconv *tos-buf precedent.
|
// overwritten on the next local() call. strconv *tos-buf precedent.
|
||||||
let localbuf: [MAX]u8 = [0...];
|
let localbuf: [MAX]u8 = [0...];
|
||||||
@@ -225,7 +223,6 @@ export fn parent(buf: *buffer) (str | error) = {
|
|||||||
return strings.frombytes(buf.buf[0:newend]);
|
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
|
// POSIX-compliant dirname/basename. They do NOT normalize the input and
|
||||||
// operate on plain str (no buffer), so they sit apart from the stack
|
// operate on plain str (no buffer), so they sit apart from the stack
|
||||||
// paradigm above — same POSIX-complaint as posix.ha:7-11.
|
// paradigm above — same POSIX-complaint as posix.ha:7-11.
|
||||||
|
|||||||
@@ -564,8 +564,6 @@ type fdexp = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// ---- fold 3: anchors / escape / postfix / alternation ----------------
|
|
||||||
|
|
||||||
// instsig — flatten an inst for the table-driven program pins below:
|
// instsig — flatten an inst for the table-driven program pins below:
|
||||||
// kind base + payload. Takes the 56B inst by value (the #19-landed
|
// kind base + payload. Takes the 56B inst by value (the #19-landed
|
||||||
// is_consuming_inst shape).
|
// 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
|
// Emitted-program pins for the `[..]` arm: the charset inst lands where
|
||||||
// a literal would (and composes with fold-3's postfix/anchors), the
|
// a literal would (and composes with fold-3's postfix/anchors), the
|
||||||
// charsets table grows one entry per bracket, negation rides
|
// 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
|
// The fold-5a compile-error surface, exact texts. ")" and "(" are
|
||||||
// Hare's own ERROR fixtures (+test.ha:276/606); "a("/"a)" graduate
|
// Hare's own ERROR fixtures (+test.ha:276/606); "a("/"a)" graduate
|
||||||
// here from the metachar-loud table with their real texts. The
|
// 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
|
// 5b compile-error rows end-to-end through compile() — the two
|
||||||
// parse_repetition texts surface verbatim (+test.ha:461-463 ERROR
|
// parse_repetition texts surface verbatim (+test.ha:461-463 ERROR
|
||||||
// rows). "a{" is the GRADUATED metachar-loud row: with `{` ported the
|
// 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
|
// compile error: `[[:` with no valid class name
|
||||||
// ref/hare/regex/+test.ha — no direct cite; error string from
|
// ref/hare/regex/+test.ha — no direct cite; error string from
|
||||||
// regex.ha:203 "No character class after '[:'".
|
// regex.ha:203 "No character class after '[:'".
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
// (ref/hare/shlex/{split.ha,escape.ha}) using ww byte-indexed cursors
|
// (ref/hare/shlex/{split.ha,escape.ha}) using ww byte-indexed cursors
|
||||||
// in place of Hare's strings::iterator.
|
// 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
|
// Owning model: split() returns a fresh `[]str` of strings.dup'd
|
||||||
// elements; release with [[strings.freeall]] (Hare's natural disposer
|
// elements; release with [[strings.freeall]] (Hare's natural disposer
|
||||||
// shape). quotestr() returns an os.alloc'd str; release via
|
// shape). quotestr() returns an os.alloc'd str; release via
|
||||||
@@ -227,7 +219,6 @@ export fn split(in: str) ([]str | syntaxerr) = {
|
|||||||
dirty = true;
|
dirty = true;
|
||||||
|
|
||||||
if (r == ' ' || r == '\t' || r == '\n') {
|
if (r == ' ' || r == '\t' || r == '\n') {
|
||||||
// Collapse a run of whitespace.
|
|
||||||
let inner: bool = true;
|
let inner: bool = true;
|
||||||
for (inner) {
|
for (inner) {
|
||||||
if (pos >= in.len) { inner = false; }
|
if (pos >= in.len) { inner = false; }
|
||||||
|
|||||||
@@ -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-
|
// Two cohorts, grouped Hare-style — one @test fn per cohort, table-
|
||||||
// driven inside via per-arity helpers:
|
// 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);
|
let _c: (void | io.error) = io.close(snk);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- split: Hare's @test fn split() table --------------------------
|
|
||||||
//
|
|
||||||
// 9 success rows + 3 syntaxerr rows ported VERBATIM from
|
// 9 success rows + 3 syntaxerr rows ported VERBATIM from
|
||||||
// ref/hare/shlex/+test.ha; plus one ww-specific edge (empty input →
|
// ref/hare/shlex/+test.ha; plus one ww-specific edge (empty input →
|
||||||
// empty []str) confirmed by drew.
|
// empty []str) confirmed by drew.
|
||||||
@@ -146,8 +141,6 @@ fn checkquote(in: str, expected: str) void = {
|
|||||||
checkempty("");
|
checkempty("");
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- quote: Hare's testquote rows + the empty-input edge ----------
|
|
||||||
//
|
|
||||||
// 4 rows from ref/hare/shlex/+test.ha @test fn quote(). The empty-
|
// 4 rows from ref/hare/shlex/+test.ha @test fn quote(). The empty-
|
||||||
// input row (→ `''`) is implementation-specific (Hare's testquote
|
// input row (→ `''`) is implementation-specific (Hare's testquote
|
||||||
// doesn't cover it) but is documented behaviour per shlex.ww's
|
// doesn't cover it) but is documented behaviour per shlex.ww's
|
||||||
@@ -163,16 +156,12 @@ fn checkquote(in: str, expected: str) void = {
|
|||||||
checkquote("@%+=:,./-", "@%+=:,./-");
|
checkquote("@%+=:,./-", "@%+=:,./-");
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- quotestr ------------------------------------------------------
|
|
||||||
|
|
||||||
@test fn test_quotestr() void = {
|
@test fn test_quotestr() void = {
|
||||||
let r: str = shlex.quotestr("hello world");
|
let r: str = shlex.quotestr("hello world");
|
||||||
assert(!(!streq(r, "'hello world'")));
|
assert(!(!streq(r, "'hello world'")));
|
||||||
// leak r — short-lived test process, same precedent as fnmatchtest.
|
// leak r — short-lived test process, same precedent as fnmatchtest.
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- strerror ------------------------------------------------------
|
|
||||||
|
|
||||||
@test fn test_strerror() void = {
|
@test fn test_strerror() void = {
|
||||||
let e: shlex.syntaxerr;
|
let e: shlex.syntaxerr;
|
||||||
let s: str = shlex.strerror(e);
|
let s: str = shlex.strerror(e);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// strconv — arbitrary-precision decimal engine for float↔string
|
// Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports Go's
|
||||||
// conversion. Mirrors ref/hare/strconv/decimal.ha (Hare in turn ports
|
// lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
|
||||||
// Go's lib/strconv/decimal.go). Pure integer arithmetic; no f32/f64
|
|
||||||
// references (#121 residual-guard SAFE).
|
// references (#121 residual-guard SAFE).
|
||||||
//
|
//
|
||||||
// Spelling divergences from Hare (mechanical, ww-side parser shape):
|
// Spelling divergences from Hare (mechanical, ww-side parser shape):
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// strconv — float→string via Ryū (shortest round-trippable decimal).
|
|
||||||
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
|
// Mirrors ref/hare/strconv/ftos_ryu.ha (the algorithm core) +
|
||||||
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
|
// ref/hare/strconv/ftos.ha:432 (the f64tos driver). Ryū: Ulf Adams,
|
||||||
// https://doi.org/10.1145/3192366.3192369 — Hare translated it from the
|
// 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 };
|
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/
|
// u64 siblings at 32-bit width; they reuse the SHARED f64computeinvpow5/
|
||||||
// f64computepow5 (and thus the f64 SPLIT2 tables) per ftos_ryu.ha — there
|
// f64computepow5 (and thus the f64 SPLIT2 tables) per ftos_ryu.ha — there
|
||||||
// is no separate f32 table. Same scalar-PARAM-mutation → copy-to-local,
|
// is no separate f32 table. Same scalar-PARAM-mutation → copy-to-local,
|
||||||
// comma-split, expr-yield → block divergences as the f64 path
|
// 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).
|
// ref/hare/strconv/ftos_ryu.ha:52. Largest p with 5^p | value (32-bit).
|
||||||
fn pow5fac32(v: u32) u32 = {
|
fn pow5fac32(v: u32) u32 = {
|
||||||
@@ -523,13 +522,13 @@ fn f32todecf32(mantissa: u32, exponent: u32) decf32 = {
|
|||||||
return decf32 { mantissa = output, exponent = (exp: i64) };
|
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
|
// 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
|
// 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
|
// 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
|
// G) and are NOT ported — porting them stubbed would be untested dead
|
||||||
// code. The parametric ftosf/ffmt/fflags surface is deferred (task #64;
|
// 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).
|
// ref/hare/strconv/ftos.ha:49. Decimal digit-count of n (n <= 1e17).
|
||||||
fn declen(n: u64) uint = {
|
fn declen(n: u64) uint = {
|
||||||
|
|||||||
@@ -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
|
// 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
|
// fold (strconv #106 fold-5): no logic, consumed by ftos.ww's
|
||||||
// f64computeinvpow5 / f64computepow5 (the Ryū power-of-five cores).
|
// f64computeinvpow5 / f64computepow5 (the Ryū power-of-five cores).
|
||||||
|
|||||||
@@ -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 /
|
// Ports ref/hare/strconv/+test/ftos_test.ha's ffmt::G / prec=void /
|
||||||
// fflags::NONE rows (the f64tos cases) verbatim — these are exactly
|
// 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/…
|
// strconv.f64tos's output. encode_f_dec is covered by 13.37/1100/0.011/…
|
||||||
|
|||||||
@@ -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
|
// 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
|
// stou.ha:116-138 (stou/stou_bases). Hare's strconv integer tests are
|
||||||
// flat assert SEQUENCES, not row-array tables — mirrored here as inline
|
// 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
|
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
|
// 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
|
// 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
|
// sequences too. Radix-literal inputs are written in DECIMAL (ww value
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// strconv — string-to-float. Mirrors ref/hare/strconv/stof.ha
|
// Mirrors ref/hare/strconv/stof.ha (Hare in turn adapts Go):
|
||||||
// (Hare in turn adapts Go): Eisel-Lemire fast path [1] with the
|
// Eisel-Lemire fast path [1] with the Simple-Decimal-Conversion slow
|
||||||
// Simple-Decimal-Conversion slow path [2] (decimal.ww) as fallback.
|
// path [2] (decimal.ww) as fallback.
|
||||||
// [1]: https://nigeltao.github.io/blog/2020/eisel-lemire.html
|
// [1]: https://nigeltao.github.io/blog/2020/eisel-lemire.html
|
||||||
// [2]: https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html
|
// [2]: https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// strconv — stof/ftos lookup tables. Mirrors ref/hare/strconv/stof_data.ha
|
// Mirrors ref/hare/strconv/stof_data.ha byte-exact. Pure-data fold (strconv #106 fold-2, was fold-3 before drew
|
||||||
// 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
|
// re-sequenced 2026-05-26): no logic, exercised transitively when fold-3's
|
||||||
// `leftshift_newdigits` lands (ref/hare/strconv/decimal.ha:35).
|
// `leftshift_newdigits` lands (ref/hare/strconv/decimal.ha:35).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -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 /
|
// Ports ref/hare/strconv/stof.ha's @test vectors (stof64 / stof32 /
|
||||||
// stofhex). Comparisons are BIT-level (math.f64bits / f32bits) so a
|
// 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
|
// 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;
|
import math;
|
||||||
|
|
||||||
|
|
||||||
// ---- unwrap helpers (bit-exact value checks) -------------------------
|
|
||||||
|
|
||||||
fn chk64(s: str, b: base, want: f64) bool = {
|
fn chk64(s: str, b: base, want: f64) bool = {
|
||||||
match (stof64(s, b)) {
|
match (stof64(s, b)) {
|
||||||
case let v: f64 => { return math.f64bits(v) == math.f64bits(want); };
|
case let v: f64 => { return math.f64bits(v) == math.f64bits(want); };
|
||||||
@@ -89,7 +85,6 @@ fn nan32(s: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- stof64 ----------------------------------------------------------
|
|
||||||
// ref/hare/strconv/stof.ha:530.
|
// ref/hare/strconv/stof.ha:530.
|
||||||
|
|
||||||
@test fn stof64_dec() void = {
|
@test fn stof64_dec() void = {
|
||||||
@@ -141,7 +136,6 @@ fn nan32(s: str) bool = {
|
|||||||
assert(!(!nan64("naN")));
|
assert(!(!nan64("naN")));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- stof32 ----------------------------------------------------------
|
|
||||||
// ref/hare/strconv/stof.ha:560.
|
// ref/hare/strconv/stof.ha:560.
|
||||||
|
|
||||||
@test fn stof32_dec() void = {
|
@test fn stof32_dec() void = {
|
||||||
@@ -180,7 +174,6 @@ fn nan32(s: str) bool = {
|
|||||||
assert(!(!chk32("9.19100241453305036800e+20", base.DEC, 9.19100241453305036800e+20f32)));
|
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,
|
// 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
|
// 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.
|
// has no hex-float literal lexer / no F*_MAX_NORMAL math consts.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
// strconv — number↔string conversions.
|
|
||||||
//
|
|
||||||
// Mirrors Hare's strconv:: surface. The *tos functions return a
|
// Mirrors Hare's strconv:: surface. The *tos functions return a
|
||||||
// `const str` view into a module-level buffer that is overwritten on
|
// `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
|
// 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
|
// fidelity; the initial value is irrelevant (only the freshly-written
|
||||||
// prefix is ever read) but the fill form is exercised (probed: emits
|
// prefix is ever read) but the fill form is exercised (probed: emits
|
||||||
// byte-identically cross-stage).
|
// 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 — convert u to a base-b numeric string. Returns a view into
|
||||||
// `u64tos_buf`, overwritten on the next call; copy via strings.dup to
|
// `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
|
// i64tos_buf — independent from u64tos_buf so i64tos's own u64tos call
|
||||||
// (the magnitude) doesn't clobber the in-flight result. 65 = 64 digits
|
// (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).
|
// 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 — convert i to a base-b numeric string. Returns a view into
|
||||||
// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32.
|
// `i64tos_buf`. Verbatim port of ref/hare/strconv/itos.ha:10-32.
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// comparetest — exercises strings.compare. A failing row aborts
|
// Vectors mirror ref/hare/strings/compare.ha (task #5 @test conversion).
|
||||||
// via the assert/abort builtin (task #5 @test conversion).
|
|
||||||
// Vectors mirror ref/hare/strings/compare.ha.
|
|
||||||
|
|
||||||
package strings_test;
|
package strings_test;
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import os;
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- join -------------------------------------------------------------
|
|
||||||
// ref/hare/strings/concat.ha:64. Rows mirror Hare's @test fn 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,
|
// (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).
|
// multibyte, empty-mid (delim still inserted around the empty slot).
|
||||||
@@ -111,4 +110,3 @@ import os;
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// containstest — exercises strings.contains. A failing row aborts
|
// Vectors mirror ref/hare/strings/contains.ha (task #5 @test conversion).
|
||||||
// via the assert/abort builtin (task #5 @test conversion).
|
|
||||||
// Vectors mirror ref/hare/strings/contains.ha.
|
|
||||||
|
|
||||||
package strings_test;
|
package strings_test;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import os;
|
|||||||
defer os.free(m.ptr: *void, m.len: u64);
|
defer os.free(m.ptr: *void, m.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- dupall -----------------------------------------------------------
|
|
||||||
// ref/hare/strings/dup.ha:55 (#6). Element reads go through
|
// ref/hare/strings/dup.ha:55 (#6). Element reads go through
|
||||||
// `&toks.ptr[i]: *str`
|
// `&toks.ptr[i]: *str`
|
||||||
// per the splitn cases (16B element copy gap, cgen.c:6515).
|
// per the splitn cases (16B element copy gap, cgen.c:6515).
|
||||||
@@ -109,4 +108,3 @@ import os;
|
|||||||
case nomem => { abort(); };
|
case nomem => { abort(); };
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// 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
|
// `s = riter(...)` mid-test to swap the iterator's direction; ww's
|
||||||
// sret-into-existing-slot path handles that fine (probed pre-port).
|
// 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(); }; };
|
match (strings.next(&rit)) { case let r: rune => void; case utf8.done => { abort(); }; };
|
||||||
assert(!(!streq(strings.iterstr(&rit), "he")));
|
assert(!(!streq(strings.iterstr(&rit), "he")));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// strings — operations over str ({ptr,len}). Hare port; see
|
// Hare port; see ref/hare/strings/.
|
||||||
// ref/hare/strings/.
|
|
||||||
//
|
//
|
||||||
// Documented divergences from Hare:
|
// Documented divergences from Hare:
|
||||||
//
|
//
|
||||||
@@ -30,8 +29,8 @@ import os;
|
|||||||
import rt;
|
import rt;
|
||||||
import types;
|
import types;
|
||||||
|
|
||||||
// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29.
|
// ref/hare/strings/utf8.ha:29. `cap` equals `len`; the slice does
|
||||||
// `cap` equals `len`; the slice does not own a separate allocation.
|
// not own a separate allocation.
|
||||||
export fn toutf8(s: str) []u8 = {
|
export fn toutf8(s: str) []u8 = {
|
||||||
let r: []u8;
|
let r: []u8;
|
||||||
r.ptr = s.ptr;
|
r.ptr = s.ptr;
|
||||||
@@ -40,8 +39,8 @@ export fn toutf8(s: str) []u8 = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// frombytes — borrowed str view of `in`. Pure reinterpret per
|
// Pure reinterpret per CLAUDE.md rule 9 carve-out;
|
||||||
// CLAUDE.md rule 9 carve-out; ref/hare/strings/utf8.ha:10.
|
// ref/hare/strings/utf8.ha:10.
|
||||||
export fn frombytes(in: []u8) str = {
|
export fn frombytes(in: []u8) str = {
|
||||||
let r: str;
|
let r: str;
|
||||||
r.ptr = in.ptr;
|
r.ptr = in.ptr;
|
||||||
@@ -49,9 +48,8 @@ export fn frombytes(in: []u8) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// compare — three-way bytewise codepoint-order comparison. Return is
|
// Return is a sign (neg/zero/pos), not an index, so it tracks Hare's
|
||||||
// a sign (neg/zero/pos), not an index, so it tracks Hare's `int`
|
// `int` rather than the str-index i32 (#8). ref/hare/strings/compare.ha:12.
|
||||||
// rather than the str-index i32 (#8). ref/hare/strings/compare.ha:12.
|
|
||||||
export fn compare(a: str, b: str) int = {
|
export fn compare(a: str, b: str) int = {
|
||||||
let n: i32 = a.len;
|
let n: i32 = a.len;
|
||||||
if (b.len < n) { n = b.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);
|
return (a.len: int) - (b.len: int);
|
||||||
};
|
};
|
||||||
|
|
||||||
// dup — allocate a fresh copy of `s`. Caller releases with
|
// Caller releases with `os.free(r.ptr, r.len: u64)`.
|
||||||
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7.
|
// ref/hare/strings/dup.ha:7.
|
||||||
export fn dup(s: str) str = {
|
export fn dup(s: str) str = {
|
||||||
let r: str;
|
let r: str;
|
||||||
r.ptr = nil;
|
r.ptr = nil;
|
||||||
@@ -77,9 +75,7 @@ export fn dup(s: str) str = {
|
|||||||
return frombytes(buf);
|
return frombytes(buf);
|
||||||
};
|
};
|
||||||
|
|
||||||
// dupall — fresh `[]str` whose elements are independent copies of
|
// Caller releases via [[freeall]]. ref/hare/strings/dup.ha:26 (#6).
|
||||||
// `s`'s elements. Caller releases via [[freeall]].
|
|
||||||
// ref/hare/strings/dup.ha:26 (#6).
|
|
||||||
//
|
//
|
||||||
// Hare gates the per-element dup behind `?` and rolls back via
|
// Hare gates the per-element dup behind `?` and rolls back via
|
||||||
// `defer if (!ok) freeall(newsl)`. ww has no `defer if`; more
|
// `defer if (!ok) freeall(newsl)`. ww has no `defer if`; more
|
||||||
@@ -113,8 +109,6 @@ export fn dupall(s: []str) ([]str | nomem) = {
|
|||||||
return newsl;
|
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.
|
// ref/hare/strings/dup.ha:38.
|
||||||
//
|
//
|
||||||
// Empty elements (`{nil, 0}` from a zero-length dup) are skipped:
|
// 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
|
// Caller releases with `os.free(r.ptr, r.len: u64)`.
|
||||||
// order. Caller releases with `os.free(r.ptr, r.len: u64)`.
|
|
||||||
// ref/hare/strings/concat.ha:5. Hare's `nomem` return is dropped:
|
// ref/hare/strings/concat.ha:5. Hare's `nomem` return is dropped:
|
||||||
// `os.alloc` aborts on OOM.
|
// `os.alloc` aborts on OOM.
|
||||||
export fn concat(strs: str...) str = {
|
export fn concat(strs: str...) str = {
|
||||||
@@ -163,8 +156,7 @@ export fn concat(strs: str...) str = {
|
|||||||
return frombytes(buf);
|
return frombytes(buf);
|
||||||
};
|
};
|
||||||
|
|
||||||
// join — fresh allocation with `delim` placed between each element of
|
// Caller releases with `os.free(r.ptr, r.len: u64)`.
|
||||||
// `strs`. Caller releases with `os.free(r.ptr, r.len: u64)`.
|
|
||||||
// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped:
|
// ref/hare/strings/concat.ha:46. Hare's `nomem` return is dropped:
|
||||||
// `os.alloc` aborts on OOM.
|
// `os.alloc` aborts on OOM.
|
||||||
export fn join(delim: str, strs: str...) str = {
|
export fn join(delim: str, strs: str...) str = {
|
||||||
@@ -203,9 +195,8 @@ export fn join(delim: str, strs: str...) str = {
|
|||||||
return frombytes(buf);
|
return frombytes(buf);
|
||||||
};
|
};
|
||||||
|
|
||||||
// utf8bytelenbounded — walk `it` forward `end` runes and return the
|
// ref/hare/strings/sub.ha:10. Aborts on short input per Hare's
|
||||||
// resulting byte offset. ref/hare/strings/sub.ha:10. Aborts on
|
// contract for the rune-wise [[sub]].
|
||||||
// short input per Hare's contract for the rune-wise [[sub]].
|
|
||||||
fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
|
fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < end) {
|
for (i < end) {
|
||||||
@@ -218,10 +209,11 @@ fn utf8bytelenbounded(it: *iterator, end: i32) i32 = {
|
|||||||
return it.offs;
|
return it.offs;
|
||||||
};
|
};
|
||||||
|
|
||||||
// sub — borrowed substring [start, end) where start/end are rune
|
// Borrowed substring; start/end are rune indices; byte-indexed
|
||||||
// indices. ref/hare/strings/sub.ha:30. Hare's 2-arg `sub(s, start)`
|
// 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
|
// 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 = {
|
export fn sub(s: str, start: i32, end: i32) str = {
|
||||||
assert(start <= end, "strings.sub: start is higher than end");
|
assert(start <= end, "strings.sub: start is higher than end");
|
||||||
let it: iterator = iter(s);
|
let it: iterator = iter(s);
|
||||||
@@ -233,8 +225,9 @@ export fn sub(s: str, start: i32, end: i32) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// bytesub — borrowed substring [start, end) where start/end are byte
|
// Borrowed substring; start/end are byte offsets, unlike the
|
||||||
// offsets. ref/hare/strings/sub.ha:59 (#7). Returns `utf8.invalid` if
|
// rune-wise [[sub]].
|
||||||
|
// ref/hare/strings/sub.ha:59 (#7). Returns `utf8.invalid` if
|
||||||
// either endpoint lands on a continuation byte (would split a
|
// either endpoint lands on a continuation byte (would split a
|
||||||
// codepoint); the equivalent Hare predicate is `s[i] & 0xc0 == 0x80`
|
// codepoint); the equivalent Hare predicate is `s[i] & 0xc0 == 0x80`
|
||||||
// at ref/hare/strings/sub.ha:72-73.
|
// 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;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes)
|
// `scratch` must hold 4 bytes; the return borrows it. Hare inlines
|
||||||
// and return the borrowed slice trimmed to the encoded length. Hare
|
// the same shape at ref/hare/strings/index.ha:132.
|
||||||
// inlines the same shape at ref/hare/strings/index.ha:132.
|
|
||||||
fn runebytes(scratch: []u8, r: rune) []u8 = {
|
fn runebytes(scratch: []u8, r: rune) []u8 = {
|
||||||
let n: i32 = utf8.encoderune(scratch, r);
|
let n: i32 = utf8.encoderune(scratch, r);
|
||||||
let s: []u8;
|
let s: []u8;
|
||||||
@@ -265,7 +257,6 @@ fn runebytes(scratch: []u8, r: rune) []u8 = {
|
|||||||
return s;
|
return s;
|
||||||
};
|
};
|
||||||
|
|
||||||
// hasprefix — true iff `in` begins with `prefix`.
|
|
||||||
// ref/hare/strings/suffix.ha:8.
|
// ref/hare/strings/suffix.ha:8.
|
||||||
export fn hasprefix(in: str, prefix: (str | rune)) bool = {
|
export fn hasprefix(in: str, prefix: (str | rune)) bool = {
|
||||||
let scratch: [4]u8;
|
let scratch: [4]u8;
|
||||||
@@ -276,7 +267,6 @@ export fn hasprefix(in: str, prefix: (str | rune)) bool = {
|
|||||||
return bytes.hasprefix(toutf8(in), p);
|
return bytes.hasprefix(toutf8(in), p);
|
||||||
};
|
};
|
||||||
|
|
||||||
// hassuffix — true iff `in` ends with `suff`.
|
|
||||||
// ref/hare/strings/suffix.ha:26.
|
// ref/hare/strings/suffix.ha:26.
|
||||||
export fn hassuffix(in: str, suff: (str | rune)) bool = {
|
export fn hassuffix(in: str, suff: (str | rune)) bool = {
|
||||||
let scratch: [4]u8;
|
let scratch: [4]u8;
|
||||||
@@ -287,8 +277,7 @@ export fn hassuffix(in: str, suff: (str | rune)) bool = {
|
|||||||
return bytes.hassuffix(toutf8(in), s);
|
return bytes.hassuffix(toutf8(in), s);
|
||||||
};
|
};
|
||||||
|
|
||||||
// byteindex — byte-wise offset of `needle` in `haystack`, or void if
|
// ref/hare/strings/index.ha:127. Rune arm encodes via
|
||||||
// absent. ref/hare/strings/index.ha:127. Rune arm encodes via
|
|
||||||
// utf8.encoderune (Hare passes the encoded slice straight to
|
// utf8.encoderune (Hare passes the encoded slice straight to
|
||||||
// bytes::index).
|
// bytes::index).
|
||||||
export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
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);
|
return bytes.index(toutf8(haystack), n);
|
||||||
};
|
};
|
||||||
|
|
||||||
// rbyteindex — byte-wise offset of the last `needle` in `haystack`.
|
|
||||||
// ref/hare/strings/index.ha:138.
|
// ref/hare/strings/index.ha:138.
|
||||||
export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||||
let scratch: [4]u8;
|
let scratch: [4]u8;
|
||||||
@@ -311,15 +299,12 @@ export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = {
|
|||||||
return bytes.rindex(toutf8(haystack), n);
|
return bytes.rindex(toutf8(haystack), n);
|
||||||
};
|
};
|
||||||
|
|
||||||
// indexstring — str-arm of [[index]]. Dual-rune-iterator walk: at each
|
// Mirrors ref/hare/strings/index.ha:59 (#10). Hare copies
|
||||||
// candidate rune index `i`, compare `haystack` from that position
|
// `rest_iter = s_iter` directly via struct assignment; ww re-seats
|
||||||
// against `needle` rune-by-rune until needle is exhausted (match) or
|
// `rest_iter` field-wise because the let-init struct-copy form
|
||||||
// a mismatch / haystack-exhaustion breaks the inner loop. Mirrors
|
// diverges between cstage and wwstage on this iterator type
|
||||||
// ref/hare/strings/index.ha:59 (#10). Hare copies `rest_iter = s_iter`
|
// (993_ww_ww + 995_self_rebuild fail, filed as #41) and rule #10
|
||||||
// directly via struct assignment; ww re-seats `rest_iter` field-wise
|
// (CLAUDE.md) forbids stage asymmetry.
|
||||||
// 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) = {
|
fn indexstring(haystack: str, needle: str) (i32 | void) = {
|
||||||
let s_iter: iterator = iter(haystack);
|
let s_iter: iterator = iter(haystack);
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -356,11 +341,10 @@ fn indexstring(haystack: str, needle: str) (i32 | void) = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// index — rune-wise offset of `needle`'s first occurrence in
|
// Rune-wise offset, not byte-wise ([[byteindex]]).
|
||||||
// `haystack`, or void if absent. ref/hare/strings/index.ha:10. The
|
// ref/hare/strings/index.ha:10. The str-arm delegates to
|
||||||
// str-arm delegates to [[indexstring]] (dual-iterator rune-by-rune
|
// [[indexstring]] (per Hare's `index_string`, #10); the rune-arm
|
||||||
// walk per Hare's `index_string`, #10); the rune-arm mirrors Hare's
|
// mirrors Hare's `index_rune` (ref/hare/strings/index.ha:31).
|
||||||
// `index_rune` (ref/hare/strings/index.ha:31).
|
|
||||||
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
||||||
match (needle) {
|
match (needle) {
|
||||||
case let s: str => return indexstring(haystack, s);
|
case let s: str => return indexstring(haystack, s);
|
||||||
@@ -381,8 +365,7 @@ export fn index(haystack: str, needle: (str | rune)) (i32 | void) = {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// rindex — rune-wise offset of `needle`'s last occurrence in
|
// Rune-wise offset. ref/hare/strings/index.ha:22. The
|
||||||
// `haystack`, or void if absent. ref/hare/strings/index.ha:22. The
|
|
||||||
// str-arm reuses `rbyteindex`; the rune-arm walks forward tracking
|
// str-arm reuses `rbyteindex`; the rune-arm walks forward tracking
|
||||||
// the most recent matching rune index (Hare's `rindex_rune` with
|
// the most recent matching rune index (Hare's `rindex_rune` with
|
||||||
// `riter` returns a byte-offset value for multibyte strings, which
|
// `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;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// contains — true iff any of `needles` occurs in `haystack`.
|
|
||||||
// ref/hare/strings/contains.ha:9.
|
// ref/hare/strings/contains.ha:9.
|
||||||
export fn contains(haystack: str, needles: (str | rune)...) bool = {
|
export fn contains(haystack: str, needles: (str | rune)...) bool = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -450,9 +432,7 @@ export fn contains(haystack: str, needles: (str | rune)...) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// trimprefix — `s` with `prefix` stripped from the front, or `s`
|
// ref/hare/strings/trim.ha:60. Borrowed view.
|
||||||
// unchanged if it doesn't start with `prefix`. Borrowed view.
|
|
||||||
// ref/hare/strings/trim.ha:60.
|
|
||||||
export fn trimprefix(input: str, prefix: str) str = {
|
export fn trimprefix(input: str, prefix: str) str = {
|
||||||
if (!hasprefix(input, prefix)) { return input; };
|
if (!hasprefix(input, prefix)) { return input; };
|
||||||
let r: str;
|
let r: str;
|
||||||
@@ -461,7 +441,7 @@ export fn trimprefix(input: str, prefix: str) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// trimsuffix — symmetric. ref/hare/strings/trim.ha:69.
|
// ref/hare/strings/trim.ha:69.
|
||||||
export fn trimsuffix(input: str, suffix: str) str = {
|
export fn trimsuffix(input: str, suffix: str) str = {
|
||||||
if (!hassuffix(input, suffix)) { return input; };
|
if (!hassuffix(input, suffix)) { return input; };
|
||||||
let r: str;
|
let r: str;
|
||||||
@@ -470,12 +450,11 @@ export fn trimsuffix(input: str, suffix: str) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// whitespace — ASCII whitespace set used by the 0-arg ltrim/rtrim/trim
|
// ASCII set for the 0-arg ltrim/rtrim/trim branches (#9).
|
||||||
// branches (#9). ref/hare/strings/trim.ha:6.
|
// ref/hare/strings/trim.ha:6.
|
||||||
let whitespace: [4]u8 = [0x20u8, 0x0Au8, 0x09u8, 0x0Du8];
|
let whitespace: [4]u8 = [0x20u8, 0x0Au8, 0x09u8, 0x0Du8];
|
||||||
|
|
||||||
// ltrim — strip leading runes that occur in `trim`. Borrowed view.
|
// Borrowed view. 0-arg strips ASCII whitespace via [[bytes.ltrim]] (#9).
|
||||||
// 0-arg strips ASCII whitespace via [[bytes.ltrim]] (#9).
|
|
||||||
// ref/hare/strings/trim.ha:11. The spread expression is inlined
|
// ref/hare/strings/trim.ha:11. The spread expression is inlined
|
||||||
// because `let ws: []u8 = whitespace[0:4]` produces a slice whose
|
// because `let ws: []u8 = whitespace[0:4]` produces a slice whose
|
||||||
// ptr doesn't track the module-level array storage (filed as #40);
|
// 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);
|
return iterstr(&it);
|
||||||
};
|
};
|
||||||
|
|
||||||
// rtrim — strip trailing runes that occur in `trim`. Borrowed view.
|
|
||||||
// 0-arg strips ASCII whitespace via [[bytes.rtrim]] (#9). Spread is
|
// 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.
|
// ref/hare/strings/trim.ha:32.
|
||||||
export fn rtrim(input: str, trim: rune...) str = {
|
export fn rtrim(input: str, trim: rune...) str = {
|
||||||
if (trim.len == 0) {
|
if (trim.len == 0) {
|
||||||
@@ -541,23 +519,22 @@ export fn rtrim(input: str, trim: rune...) str = {
|
|||||||
return iterstr(&it);
|
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 = {
|
export fn trim(input: str, trim: rune...) str = {
|
||||||
return ltrim(rtrim(input, trim...), trim...);
|
return ltrim(rtrim(input, trim...), trim...);
|
||||||
};
|
};
|
||||||
|
|
||||||
// iterator — UTF-8 rune cursor over a `str`. Layout flattens Hare's
|
// Layout flattens Hare's anonymous-embedded `utf8::decoder`
|
||||||
// anonymous-embedded `utf8::decoder` (ref/hare/strings/iter.ha:6-9) to
|
// (ref/hare/strings/iter.ha:6-9) to explicit fields. `reverse` selects
|
||||||
// explicit fields. `reverse` selects walk direction: forward iterators
|
// walk direction: forward iterators (`iter`) advance through utf8.next;
|
||||||
// (`iter`) advance through utf8.next; reverse iterators (`riter`) advance
|
// reverse iterators (`riter`) advance through utf8.prev. May be copied
|
||||||
// through utf8.prev. May be copied to save state.
|
// to save state.
|
||||||
export type iterator = struct {
|
export type iterator = struct {
|
||||||
offs: i32,
|
offs: i32,
|
||||||
src: []u8,
|
src: []u8,
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
// iter — initialize a forward iterator at the start of `src`.
|
|
||||||
// ref/hare/strings/iter.ha:24.
|
// ref/hare/strings/iter.ha:24.
|
||||||
export fn iter(src: str) iterator = {
|
export fn iter(src: str) iterator = {
|
||||||
let r: iterator;
|
let r: iterator;
|
||||||
@@ -567,8 +544,6 @@ export fn iter(src: str) iterator = {
|
|||||||
return r;
|
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.
|
// ref/hare/strings/iter.ha:32.
|
||||||
export fn riter(src: str) iterator = {
|
export fn riter(src: str) iterator = {
|
||||||
let r: iterator;
|
let r: iterator;
|
||||||
@@ -578,8 +553,7 @@ export fn riter(src: str) iterator = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// move — private dispatch shared by next/prev. `forward` selects
|
// Aborts on more/invalid per Hare's
|
||||||
// utf8.next vs utf8.prev. Aborts on more/invalid per Hare's
|
|
||||||
// ref/hare/strings/iter.ha:51-58 ("Invalid UTF-8 string (this should
|
// 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
|
// not happen)"). Hare picks the utf8 function via a fn-pointer; ww
|
||||||
// branches on `forward` at each call site instead.
|
// 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
|
// ref/hare/strings/iter.ha:45.
|
||||||
// through utf8.next; reverse iterators (riter) step backward through
|
|
||||||
// utf8.prev. Returns utf8.done at end-of-walk. ref/hare/strings/iter.ha:45.
|
|
||||||
export fn next(it: *iterator) (rune | utf8.done) = {
|
export fn next(it: *iterator) (rune | utf8.done) = {
|
||||||
return move(!it.reverse, it);
|
return move(!it.reverse, it);
|
||||||
};
|
};
|
||||||
|
|
||||||
// prev — step back one rune. Dual to next: on a forward iterator
|
// ref/hare/strings/iter.ha:49.
|
||||||
// this walks utf8.prev; on a reverse iterator (riter) it walks
|
|
||||||
// utf8.next. ref/hare/strings/iter.ha:49.
|
|
||||||
export fn prev(it: *iterator) (rune | utf8.done) = {
|
export fn prev(it: *iterator) (rune | utf8.done) = {
|
||||||
return move(it.reverse, it);
|
return move(it.reverse, it);
|
||||||
};
|
};
|
||||||
|
|
||||||
// iterstr — borrowed view of the bytes remaining in the iterator's
|
// Borrowed view. Forward iter: bytes from offs to end; reverse iter:
|
||||||
// walk direction. Forward iter: bytes from offs to end; reverse iter:
|
|
||||||
// bytes from start to offs. ref/hare/strings/iter.ha:63.
|
// bytes from start to offs. ref/hare/strings/iter.ha:63.
|
||||||
export fn iterstr(it: *iterator) str = {
|
export fn iterstr(it: *iterator) str = {
|
||||||
let r: []u8;
|
let r: []u8;
|
||||||
@@ -634,7 +603,6 @@ export fn iterstr(it: *iterator) str = {
|
|||||||
return frombytes(r);
|
return frombytes(r);
|
||||||
};
|
};
|
||||||
|
|
||||||
// slice — borrowed substring between two iterator positions.
|
|
||||||
// ref/hare/strings/iter.ha:75. Hare passes `*iterator` directly where
|
// ref/hare/strings/iter.ha:75. Hare passes `*iterator` directly where
|
||||||
// `*utf8::decoder` is expected via anonymous-embed coercion; ww has
|
// `*utf8::decoder` is expected via anonymous-embed coercion; ww has
|
||||||
// no anonymous embed, so we reconstruct a local utf8.decoder for each
|
// 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));
|
return frombytes(utf8.slice(&b, &e));
|
||||||
};
|
};
|
||||||
|
|
||||||
// position — byte-wise offset of the iterator in its source.
|
|
||||||
// ref/hare/strings/iter.ha:82.
|
// ref/hare/strings/iter.ha:82.
|
||||||
export fn position(it: *iterator) i32 = {
|
export fn position(it: *iterator) i32 = {
|
||||||
return it.offs;
|
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
|
// First cross-module type alias in tree; needs #22's transitive
|
||||||
// alias-chain unwrap (cstage type_chase_named + wwstage
|
// alias-chain unwrap (cstage type_chase_named + wwstage
|
||||||
// structlookupchain) to walk struct fields through the chain.
|
// structlookupchain) to walk struct fields through the chain.
|
||||||
export type tokenizer = bytes.tokenizer;
|
export type tokenizer = bytes.tokenizer;
|
||||||
|
|
||||||
// tokenize — yield substrings of `s` split on any byte in `delim`.
|
// `s` and `delim` are borrowed; caller keeps them live for the
|
||||||
// Leading / trailing / adjacent delims yield empty tokens. `s` and
|
// tokenizer's lifetime. ref/hare/strings/tokenize.ha:32. ASCII-only delim
|
||||||
// `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
|
// asserted per Hare lines 35-37: a multibyte rune in delim would
|
||||||
// split on a single continuation byte and yield invalid UTF-8.
|
// split on a single continuation byte and yield invalid UTF-8.
|
||||||
export fn tokenize(s: str, delim: str) tokenizer = {
|
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...);
|
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.
|
// ref/hare/strings/tokenize.ha:44.
|
||||||
export fn rtokenize(s: str, delim: str) tokenizer = {
|
export fn rtokenize(s: str, delim: str) tokenizer = {
|
||||||
let d: []u8 = toutf8(delim);
|
let d: []u8 = toutf8(delim);
|
||||||
@@ -692,7 +655,6 @@ export fn rtokenize(s: str, delim: str) tokenizer = {
|
|||||||
return bytes.rtokenize(toutf8(s), d...);
|
return bytes.rtokenize(toutf8(s), d...);
|
||||||
};
|
};
|
||||||
|
|
||||||
// nexttoken — current token, advancing the cursor.
|
|
||||||
// ref/hare/strings/tokenize.ha:62.
|
// ref/hare/strings/tokenize.ha:62.
|
||||||
export fn nexttoken(s: *tokenizer) (str | bytes.done) = {
|
export fn nexttoken(s: *tokenizer) (str | bytes.done) = {
|
||||||
let b: *bytes.tokenizer = s: *bytes.tokenizer;
|
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.
|
// ref/hare/strings/tokenize.ha:71.
|
||||||
export fn peektoken(s: *tokenizer) (str | bytes.done) = {
|
export fn peektoken(s: *tokenizer) (str | bytes.done) = {
|
||||||
let b: *bytes.tokenizer = s: *bytes.tokenizer;
|
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
|
// ref/hare/strings/tokenize.ha:79.
|
||||||
// cursor. ref/hare/strings/tokenize.ha:79.
|
|
||||||
export fn remainingtokens(s: *tokenizer) str = {
|
export fn remainingtokens(s: *tokenizer) str = {
|
||||||
let b: *bytes.tokenizer = s: *bytes.tokenizer;
|
let b: *bytes.tokenizer = s: *bytes.tokenizer;
|
||||||
return frombytes(bytes.remainingtokens(b));
|
return frombytes(bytes.remainingtokens(b));
|
||||||
};
|
};
|
||||||
|
|
||||||
// cut — split `in` along the first instance of `delim`, returning the
|
// When `delim` is absent the whole input is the first half and the
|
||||||
// portions before and after it. When `delim` is absent the whole input
|
// second is empty. Both halves are borrowed from `in`; caller
|
||||||
// is the first half and the second is empty. Both halves are borrowed
|
// ensures `delim` is non-empty. ref/hare/strings/tokenize.ha:288.
|
||||||
// from `in`; caller ensures `delim` is non-empty.
|
|
||||||
// ref/hare/strings/tokenize.ha:288.
|
|
||||||
export fn cut(in: str, delim: str) (str, str) = {
|
export fn cut(in: str, delim: str) (str, str) = {
|
||||||
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
|
let (a, b) = bytes.cut(toutf8(in), toutf8(delim));
|
||||||
return (frombytes(a), frombytes(b));
|
return (frombytes(a), frombytes(b));
|
||||||
};
|
};
|
||||||
|
|
||||||
// rcut — like [[cut]] but split along the LAST instance of `delim`.
|
|
||||||
// ref/hare/strings/tokenize.ha:302.
|
// ref/hare/strings/tokenize.ha:302.
|
||||||
export fn rcut(in: str, delim: str) (str, str) = {
|
export fn rcut(in: str, delim: str) (str, str) = {
|
||||||
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
|
let (a, b) = bytes.rcut(toutf8(in), toutf8(delim));
|
||||||
return (frombytes(a), frombytes(b));
|
return (frombytes(a), frombytes(b));
|
||||||
};
|
};
|
||||||
|
|
||||||
// splitn — split `in` on any byte in `delim`, returning up to `n`
|
// The trailing slot (when more than `n - 1` tokens exist) holds the
|
||||||
// tokens via forward iteration. The trailing slot (when more than
|
// unconsumed remainder. Strings within the result are borrowed
|
||||||
// `n - 1` tokens exist) holds the unconsumed remainder. Strings
|
// from `in`.
|
||||||
// within the result are borrowed from `in`.
|
|
||||||
//
|
//
|
||||||
// The caller frees the returned slice via
|
// The caller frees the returned slice via
|
||||||
// `os.free(r.ptr: *void, (r.cap: u64) * size(str): u64)`.
|
// `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;
|
return toks;
|
||||||
};
|
};
|
||||||
|
|
||||||
// rsplitn — reverse-direction counterpart to [[splitn]]: tokens are
|
// The trailing slot holds the unconsumed prefix (everything before
|
||||||
// collected from the end of `in`. The trailing slot holds the
|
// the n-th-from-last delim hit).
|
||||||
// unconsumed prefix (everything before the n-th-from-last delim hit).
|
|
||||||
//
|
//
|
||||||
// When the input has fewer than n tokens, the `done` short-circuit
|
// When the input has fewer than n tokens, the `done` short-circuit
|
||||||
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
|
// 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;
|
return toks;
|
||||||
};
|
};
|
||||||
|
|
||||||
// split — full split of `in` on `delim` (no token cap). Mirrors
|
// Mirrors `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
|
||||||
// `splitn(in, delim, types::SIZE_MAX)`. ww uses `types.I32_MAX`
|
|
||||||
// because the index type is i32 (lib/CLAUDE.md).
|
// because the index type is i32 (lib/CLAUDE.md).
|
||||||
//
|
//
|
||||||
// ref/hare/strings/tokenize.ha:242.
|
// 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);
|
return splitn(in, delim, types.I32_MAX);
|
||||||
};
|
};
|
||||||
|
|
||||||
// lpad — left-pad `s` with `p` rune until the result reaches `maxlen`
|
// Length comparison is BYTES, mirroring Hare's `len(s) >= maxlen`
|
||||||
// bytes. Length comparison is BYTES, mirroring Hare's `len(s) >= maxlen`
|
|
||||||
// at ref/hare/strings/pad.ha:9. A multibyte `p` whose encoded width
|
// 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
|
// doesn't divide `maxlen - s.len` evenly leaves a trailing pad byte
|
||||||
// pair sliced mid-codepoint at byte `maxlen-1`, exactly as Hare's
|
// 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);
|
return frombytes(buf);
|
||||||
};
|
};
|
||||||
|
|
||||||
// replace — fresh allocation of `s` with every non-overlapping
|
// Replaces every non-overlapping occurrence of `needle`. Caller
|
||||||
// occurrence of `needle` replaced by `target`. Caller releases with
|
// releases with `os.free(r.ptr, r.len: u64)`.
|
||||||
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/replace.ha:8 (#4).
|
// ref/hare/strings/replace.ha:8 (#4).
|
||||||
//
|
//
|
||||||
// Hare delegates to [[multireplace]] with a single pair; ww has no
|
// Hare delegates to [[multireplace]] with a single pair; ww has no
|
||||||
// `(str, str)` variadic shape today (#39), so this is a standalone
|
// `(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);
|
return frombytes(res);
|
||||||
};
|
};
|
||||||
|
|
||||||
// rpad — right-pad `s` with `p` rune until the result reaches `maxlen`
|
// Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
|
||||||
// bytes. Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
|
|
||||||
export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||||
if (s.len >= maxlen) { return dup(s); };
|
if (s.len >= maxlen) { return dup(s); };
|
||||||
let scratch: [4]u8;
|
let scratch: [4]u8;
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
// suffixtest — exercises strings.hasprefix/hassuffix. A failing row
|
// Vectors mirror ref/hare/strings/suffix.ha (task #5 @test conversion).
|
||||||
// aborts via the assert/abort builtin (task #5 @test conversion).
|
|
||||||
// Vectors mirror ref/hare/strings/suffix.ha.
|
|
||||||
|
|
||||||
package strings_test;
|
package strings_test;
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,6 @@ fn expect_str_done(t: *strings.tokenizer) void = {
|
|||||||
assert(!(!streq(strings.remainingtokens(&t2), "a b c")));
|
assert(!(!streq(strings.remainingtokens(&t2), "a b c")));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- splitn / rsplitn / split ----------------------------------------
|
|
||||||
// ref/hare/strings/tokenize.ha:245 @test fn split. Hare's vectors
|
// ref/hare/strings/tokenize.ha:245 @test fn split. Hare's vectors
|
||||||
// mirrored here directly; element reads go through `&toks.ptr[i]: *str`
|
// 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
|
// 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);
|
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
|
// ref/hare/strings/tokenize.ha:316 (@test fn cut). str wrappers over
|
||||||
// bytes.cut/rcut. The `let (a, b) = cut(...)` destructure drives the
|
// bytes.cut/rcut. The `let (a, b) = cut(...)` destructure drives the
|
||||||
// over-cap tuple-return (sret) path end-to-end (a 2nd #10 witness).
|
// over-cap tuple-return (sret) path end-to-end (a 2nd #10 witness).
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import strings;
|
|||||||
assert(!(!streq(strings.trimsuffix("hello", "hello"), "")));
|
assert(!(!streq(strings.trimsuffix("hello", "hello"), "")));
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- ltrim / rtrim / trim ---------------------------------------------
|
|
||||||
// ref/hare/strings/trim.ha:75-97. 0-arg rows (#9) pin the ASCII
|
// 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).
|
// whitespace set (' ', '\t', '\n', '\r' — ref/hare/strings/trim.ha:6).
|
||||||
|
|
||||||
@@ -167,4 +166,3 @@ import strings;
|
|||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -113,9 +113,8 @@ fn nextrand() u64 = {
|
|||||||
// exposes getenv; documented in the file header.
|
// exposes getenv; documented in the file header.
|
||||||
fn gettmpdir() str = { return "/tmp"; };
|
fn gettmpdir() str = { return "/tmp"; };
|
||||||
|
|
||||||
// puts — copy `s` into pathbuf starting at `off`. Caps writes
|
// Caps writes against pathbuf's capacity so a long caller-supplied
|
||||||
// against pathbuf's capacity so a long caller-supplied dir can't
|
// dir can't run off the end.
|
||||||
// run off the end. Returns the new offset.
|
|
||||||
fn puts(off: i32, s: str) i32 = {
|
fn puts(off: i32, s: str) i32 = {
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
for (i < s.len) {
|
for (i < s.len) {
|
||||||
@@ -126,7 +125,6 @@ fn puts(off: i32, s: str) i32 = {
|
|||||||
return off + i;
|
return off + i;
|
||||||
};
|
};
|
||||||
|
|
||||||
// puthex — 16 lowercase hex digits of `v` into pathbuf[off..off+16].
|
|
||||||
fn puthex(off: i32, v: u64) i32 = {
|
fn puthex(off: i32, v: u64) i32 = {
|
||||||
let hex: str = "0123456789abcdef";
|
let hex: str = "0123456789abcdef";
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
|
|||||||
@@ -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
|
// Every @test enumerates parallel `[N]T` arrays of inputs and
|
||||||
// expectations, then iterates one body across them. Parallel arrays
|
// expectations, then iterates one body across them. Parallel arrays
|
||||||
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
|
// (rather than `[N]struct{...}`) sidestep the cstage cgen's chained
|
||||||
@@ -28,8 +25,6 @@ fn streq(a: str, b: str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- namedroundtrip: write + read-back across payload sizes ------------
|
|
||||||
|
|
||||||
@test fn namedroundtrip() void = {
|
@test fn namedroundtrip() void = {
|
||||||
// (payload size, fill byte). Row 0 covers the zero-byte edge.
|
// (payload size, fill byte). Row 0 covers the zero-byte edge.
|
||||||
let sz: [4]i32;
|
let sz: [4]i32;
|
||||||
@@ -56,7 +51,6 @@ fn streq(a: str, b: str) bool = {
|
|||||||
assert(!(!streq(strslice(p, 0, 10), "/tmp/temp.")));
|
assert(!(!streq(strslice(p, 0, 10), "/tmp/temp.")));
|
||||||
assert(!(p.ptr[p.len] != 0u8));
|
assert(!(p.ptr[p.len] != 0u8));
|
||||||
|
|
||||||
// Build fill payload, write it, lseek to 0, read it back.
|
|
||||||
let wbuf: [64]u8;
|
let wbuf: [64]u8;
|
||||||
let k: i32 = 0;
|
let k: i32 = 0;
|
||||||
for (k < sz[i]) { wbuf[k] = fill[i]; k += 1; };
|
for (k < sz[i]) { wbuf[k] = fill[i]; k += 1; };
|
||||||
@@ -82,13 +76,11 @@ fn streq(a: str, b: str) bool = {
|
|||||||
k2 += 1;
|
k2 += 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
// File exists pre-cleanup.
|
|
||||||
assert(!(os.access(p, 0i32) != 0));
|
assert(!(os.access(p, 0i32) != 0));
|
||||||
|
|
||||||
assert(!(os.close(fd) != 0));
|
assert(!(os.close(fd) != 0));
|
||||||
assert(!(os.remove(p) != 0));
|
assert(!(os.remove(p) != 0));
|
||||||
|
|
||||||
// Cleanup landed.
|
|
||||||
assert(!(os.access(p, 0i32) == 0));
|
assert(!(os.access(p, 0i32) == 0));
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -104,8 +96,6 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- namedoverwrite: static buffer is reused across calls --------------
|
|
||||||
//
|
|
||||||
// Hare docs: "The name is statically allocated, and will be
|
// Hare docs: "The name is statically allocated, and will be
|
||||||
// overwritten on subsequent calls." Match that contract — the second
|
// overwritten on subsequent calls." Match that contract — the second
|
||||||
// named() call lands in the same buffer, so p1.ptr == p2.ptr.
|
// 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(!(p1.ptr != p2.ptr));
|
||||||
assert(!(streq(strslice(p2, 0, p2.len), psnap)));
|
assert(!(streq(strslice(p2, 0, p2.len), psnap)));
|
||||||
|
|
||||||
// Both fds are distinct.
|
|
||||||
assert(!(fd1 == fd2));
|
assert(!(fd1 == fd2));
|
||||||
|
|
||||||
assert(!(os.close(fd2) != 0));
|
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
|
// Coverage for the underlying open+create+EXCL path lives in
|
||||||
// [[namedroundtrip]] / [[namedoverwrite]].
|
// [[namedroundtrip]] / [[namedoverwrite]].
|
||||||
|
|
||||||
// ---- dirlifecycle: empty dir, then dir + one child file ----------------
|
|
||||||
|
|
||||||
@test fn dirlifecycle() void = {
|
@test fn dirlifecycle() void = {
|
||||||
// (child count). Row 0: empty dir. Row 1: dir + one file.
|
// (child count). Row 0: empty dir. Row 1: dir + one file.
|
||||||
let childn: [2]i32;
|
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(!(!streq(strslice(d, 0, 5), "/tmp/")));
|
||||||
assert(!(d.ptr[d.len] != 0u8));
|
assert(!(d.ptr[d.len] != 0u8));
|
||||||
|
|
||||||
// Dir exists.
|
|
||||||
assert(!(os.access(d, 0i32) != 0));
|
assert(!(os.access(d, 0i32) != 0));
|
||||||
|
|
||||||
// Snapshot the dir path into a local NUL-terminated buffer:
|
// 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;
|
dview.ptr = &dsnap[0]; dview.len = dlen;
|
||||||
|
|
||||||
if (childn[i] > 0) {
|
if (childn[i] > 0) {
|
||||||
// Build "<d>/x\0" in a local buffer.
|
|
||||||
let cbuf: [144]u8;
|
let cbuf: [144]u8;
|
||||||
let off: i32 = 0;
|
let off: i32 = 0;
|
||||||
let j: i32 = 0;
|
let j: i32 = 0;
|
||||||
@@ -217,15 +202,12 @@ fn strslice(p: str, lo: i32, hi: i32) str = {
|
|||||||
// touched between dir() and here).
|
// touched between dir() and here).
|
||||||
assert(!(os.rmdir(dview) != 0));
|
assert(!(os.rmdir(dview) != 0));
|
||||||
|
|
||||||
// Cleanup landed.
|
|
||||||
assert(!(os.access(dview, 0i32) == 0));
|
assert(!(os.access(dview, 0i32) == 0));
|
||||||
|
|
||||||
i += 1;
|
i += 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- diruniqueness: two dir() calls produce different paths ------------
|
|
||||||
|
|
||||||
@test fn diruniqueness() void = {
|
@test fn diruniqueness() void = {
|
||||||
let d1: str = temp.dir();
|
let d1: str = temp.dir();
|
||||||
let snap: [128]u8;
|
let snap: [128]u8;
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ export fn sleep(d: duration, c: clock) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/time/arithm.ha:9. Adds duration to instant. The
|
// ref/hare/time/arithm.ha:9. The negative-duration branch normalises
|
||||||
// negative-duration branch normalises nsec into [0, second).
|
// nsec into [0, second).
|
||||||
export fn add(i: instant, x: duration) instant = {
|
export fn add(i: instant, x: duration) instant = {
|
||||||
let r: instant;
|
let r: instant;
|
||||||
let xi: i64 = x: i64;
|
let xi: i64 = x: i64;
|
||||||
@@ -100,8 +100,7 @@ export fn add(i: instant, x: duration) instant = {
|
|||||||
return r;
|
return r;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ref/hare/time/arithm.ha:26. Returns duration from a to b.
|
// ref/hare/time/arithm.ha:26. Sign convention: b - a.
|
||||||
// Sign convention: b - a.
|
|
||||||
export fn diff(a: instant, b: instant) duration = {
|
export fn diff(a: instant, b: instant) duration = {
|
||||||
let sec: i64 = second: i64;
|
let sec: i64 = second: i64;
|
||||||
let v: i64 = ((b.sec - a.sec) * sec) + (b.nsec - a.nsec);
|
let v: i64 = ((b.sec - a.sec) * sec) + (b.nsec - a.nsec);
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
// lib/ww/syntax/ast.ww — port of cmd/wcc/ast.c (Node defs + printer).
|
// 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.
|
|
||||||
//
|
//
|
||||||
// Calling-convention shim: same as tok/lex — `node` is too big to pass
|
// 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
|
// by value (8 *node pointers + 2 strs + a few ints), so callers always
|
||||||
@@ -12,14 +9,10 @@ package syntax;
|
|||||||
import os;
|
import os;
|
||||||
import strconv;
|
import strconv;
|
||||||
|
|
||||||
// ---- Nkind ------------------------------------------------------------
|
// 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
|
||||||
// Mirror of cmd/wcc/ww.h Nkind. Values must stay numerically equal
|
// C side. Tail-appended entries (TYPETEST onward) preserve every prior
|
||||||
// to the C side (rule-6 data-shape mirror).
|
// N_* value.
|
||||||
|
|
||||||
// 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.
|
|
||||||
export type nkind = enum i32 {
|
export type nkind = enum i32 {
|
||||||
N_NONE = 0,
|
N_NONE = 0,
|
||||||
|
|
||||||
@@ -112,8 +105,6 @@ export type nkind = enum i32 {
|
|||||||
N_LAST = 68,
|
N_LAST = 68,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Node -------------------------------------------------------------
|
|
||||||
|
|
||||||
export type node = struct {
|
export type node = struct {
|
||||||
kind: nkind,
|
kind: nkind,
|
||||||
file: str,
|
file: str,
|
||||||
@@ -150,8 +141,6 @@ export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
|
|||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- printer ----------------------------------------------------------
|
|
||||||
|
|
||||||
export fn nkname(k: nkind) str = {
|
export fn nkname(k: nkind) str = {
|
||||||
switch (k) {
|
switch (k) {
|
||||||
case nkind.N_NONE: return "none";
|
case nkind.N_NONE: return "none";
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
// asttest — functional-equivalence pin for [[nkname]] after the
|
// Functional-equivalence pin for [[nkname]] after the
|
||||||
// if-ladder → switch fold (struct fold S2). Run with
|
// if-ladder → switch fold (struct fold S2).
|
||||||
// `ww run -I lib/ww lib/ww/syntax/asttest.ww`.
|
|
||||||
//
|
//
|
||||||
// nkname is checked against every nkind value (the full ladder the
|
// nkname is checked against every nkind value (the full ladder the
|
||||||
// switch replaced) plus the out-of-band fallback ("?"). A failing row
|
// switch replaced) plus the out-of-band fallback ("?"). A failing row
|
||||||
// aborts via the assert/abort builtin (task #5 @test conversion).
|
// aborts via the assert/abort builtin (task #5 @test conversion).
|
||||||
// `package main` + bare `import ast` mirrors wwdump (the external
|
|
||||||
// astprint consumer).
|
|
||||||
|
|
||||||
package syntax_test;
|
package syntax_test;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
package syntax;
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ fn parseuse(p: *parser) *node = {
|
|||||||
let pf: str = p.curfile;
|
let pf: str = p.curfile;
|
||||||
let pl: i32 = p.curline;
|
let pl: i32 = p.curline;
|
||||||
let pc: i32 = p.curcol;
|
let pc: i32 = p.curcol;
|
||||||
advance(p); // past `use`
|
advance(p);
|
||||||
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
|
let n: *node = newnode(nkind.N_USE, pf, pl, pc);
|
||||||
n.nmod = p.curmod;
|
n.nmod = p.curmod;
|
||||||
// M1 #22: accumulate the full dotted import path (n.usepath) for the
|
// M1 #22: accumulate the full dotted import path (n.usepath) for the
|
||||||
@@ -24,7 +24,7 @@ fn parseuse(p: *parser) *node = {
|
|||||||
expectident(p, &leaf);
|
expectident(p, &leaf);
|
||||||
let path: str = leaf;
|
let path: str = leaf;
|
||||||
for (p.curkind == tkind.TK_DOT) {
|
for (p.curkind == tkind.TK_DOT) {
|
||||||
advance(p); // past `.`
|
advance(p);
|
||||||
expectident(p, &leaf);
|
expectident(p, &leaf);
|
||||||
path = strings.concat(path, ".", leaf);
|
path = strings.concat(path, ".", leaf);
|
||||||
};
|
};
|
||||||
@@ -38,7 +38,7 @@ fn parsedef(p: *parser, exported: i32) *node = {
|
|||||||
let pf: str = p.curfile;
|
let pf: str = p.curfile;
|
||||||
let pl: i32 = p.curline;
|
let pl: i32 = p.curline;
|
||||||
let pc: i32 = p.curcol;
|
let pc: i32 = p.curcol;
|
||||||
advance(p); // past `def`
|
advance(p);
|
||||||
let n: *node = newnode(nkind.N_DEF, pf, pl, pc);
|
let n: *node = newnode(nkind.N_DEF, pf, pl, pc);
|
||||||
n.nmod = p.curmod;
|
n.nmod = p.curmod;
|
||||||
let id: str;
|
let id: str;
|
||||||
@@ -160,7 +160,7 @@ fn parsefn(p: *parser, exported: i32, attrs: *node) *node = {
|
|||||||
let pf: str = p.curfile;
|
let pf: str = p.curfile;
|
||||||
let pl: i32 = p.curline;
|
let pl: i32 = p.curline;
|
||||||
let pc: i32 = p.curcol;
|
let pc: i32 = p.curcol;
|
||||||
advance(p); // past `fn`
|
advance(p);
|
||||||
let n: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
|
let n: *node = newnode(nkind.N_FNDECL, pf, pl, pc);
|
||||||
n.nmod = p.curmod;
|
n.nmod = p.curmod;
|
||||||
let id: str;
|
let id: str;
|
||||||
@@ -190,7 +190,7 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
|
|||||||
let pf: str = p.curfile;
|
let pf: str = p.curfile;
|
||||||
let pl: i32 = p.curline;
|
let pl: i32 = p.curline;
|
||||||
let pc: i32 = p.curcol;
|
let pc: i32 = p.curcol;
|
||||||
advance(p); // past `type`
|
advance(p);
|
||||||
let n: *node = newnode(nkind.N_TYPEDECL, pf, pl, pc);
|
let n: *node = newnode(nkind.N_TYPEDECL, pf, pl, pc);
|
||||||
n.nmod = p.curmod;
|
n.nmod = p.curmod;
|
||||||
let id: str;
|
let id: str;
|
||||||
@@ -202,4 +202,3 @@ fn parsetypedecl(p: *parser, exported: i32) *node = {
|
|||||||
n.exported = exported;
|
n.exported = exported;
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
package syntax;
|
||||||
|
|
||||||
import os;
|
import os;
|
||||||
|
|
||||||
// streqlocal — str-to-str compare. Inlined here to avoid a cross-
|
// Inlined to avoid a cross-module `use sym;` for one call site.
|
||||||
// module `use sym;` for one call site.
|
|
||||||
fn streqlocal(a: str, b: str) bool = {
|
fn streqlocal(a: str, b: str) bool = {
|
||||||
if (a.len != b.len) { return false; };
|
if (a.len != b.len) { return false; };
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
@@ -113,7 +112,6 @@ fn parseprimary(p: *parser) *node = {
|
|||||||
if (p.curkind == tkind.TK_LPAREN) {
|
if (p.curkind == tkind.TK_LPAREN) {
|
||||||
advance(p);
|
advance(p);
|
||||||
let e: *node = parseexpr(p);
|
let e: *node = parseexpr(p);
|
||||||
// Tuple literal: (a, b, ...)
|
|
||||||
if (accepttok(p, tkind.TK_COMMA)) {
|
if (accepttok(p, tkind.TK_COMMA)) {
|
||||||
let t: *node = newnode(nkind.N_TUPLE, pf, pl, pc);
|
let t: *node = newnode(nkind.N_TUPLE, pf, pl, pc);
|
||||||
t.list = e;
|
t.list = e;
|
||||||
@@ -194,7 +192,7 @@ fn parseprimary(p: *parser) *node = {
|
|||||||
let cf: str = p.curfile;
|
let cf: str = p.curfile;
|
||||||
let cl: i32 = p.curline;
|
let cl: i32 = p.curline;
|
||||||
let cc: i32 = p.curcol;
|
let cc: i32 = p.curcol;
|
||||||
advance(p); // past `case`
|
advance(p);
|
||||||
let mc: *node = newnode(nkind.N_MCASE, cf, cl, cc);
|
let mc: *node = newnode(nkind.N_MCASE, cf, cl, cc);
|
||||||
if (p.curkind == tkind.TK_LET) {
|
if (p.curkind == tkind.TK_LET) {
|
||||||
advance(p);
|
advance(p);
|
||||||
@@ -545,4 +543,3 @@ fn parseexpr(p: *parser) *node = {
|
|||||||
};
|
};
|
||||||
return e;
|
return e;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// 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
|
// version exactly; any divergence surfaces as a cs/ww byte split in
|
||||||
@@ -18,9 +18,8 @@ import strings;
|
|||||||
import strconv;
|
import strconv;
|
||||||
import encoding.utf8;
|
import encoding.utf8;
|
||||||
|
|
||||||
// isidstart / isidpart — identifier classification. Lexer-local
|
// Lexer-local because the "alpha or '_' / alnum or '_'" set isn't part
|
||||||
// because the "alpha or '_' / alnum or '_'" set isn't part of Hare's
|
// of Hare's ascii::; ascii::isalpha + the '_' check live here instead.
|
||||||
// ascii::; ascii::isalpha + the '_' check live here instead.
|
|
||||||
fn isidstart(c: rune) bool = {
|
fn isidstart(c: rune) bool = {
|
||||||
if (ascii.isalpha(c)) { return true; };
|
if (ascii.isalpha(c)) { return true; };
|
||||||
if (c == '_') { return true; };
|
if (c == '_') { return true; };
|
||||||
@@ -33,8 +32,6 @@ fn isidpart(c: rune) bool = {
|
|||||||
return false;
|
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) = {
|
fn hexval(c: rune) (i32 | void) = {
|
||||||
if (ascii.isdigit(c)) { return (c - '0'): i32; };
|
if (ascii.isdigit(c)) { return (c - '0'): i32; };
|
||||||
if (c >= 'A') {
|
if (c >= 'A') {
|
||||||
@@ -81,7 +78,6 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
|
|||||||
l.modresetpathset = 0;
|
l.modresetpathset = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// srcb — byte at offset; helper that lifts the cast out of indexing.
|
|
||||||
fn srcb(l: *lex, off: u64) i32 = {
|
fn srcb(l: *lex, off: u64) i32 = {
|
||||||
let i: i32 = off: i32;
|
let i: i32 = off: i32;
|
||||||
let b: u8 = l.src[i];
|
let b: u8 = l.src[i];
|
||||||
@@ -139,7 +135,7 @@ fn skipws(l: *lex) bool = {
|
|||||||
if (c == '/') {
|
if (c == '/') {
|
||||||
let c2: i32 = lpeek(l, 1u64);
|
let c2: i32 = lpeek(l, 1u64);
|
||||||
if (c2 == '/') {
|
if (c2 == '/') {
|
||||||
lget(l); lget(l); // consume '//'
|
lget(l); lget(l);
|
||||||
// #16 opt-B: recognize the driver's curmod-reset
|
// #16 opt-B: recognize the driver's curmod-reset
|
||||||
// boundary directive `//ww:module-reset` (whole
|
// boundary directive `//ww:module-reset` (whole
|
||||||
// line) and flag it; lexnext emits TK_MODRESET.
|
// line) and flag it; lexnext emits TK_MODRESET.
|
||||||
@@ -371,7 +367,6 @@ fn escape(l: *lex, out: *i32) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// scandecimalrun — consume a run of decimal digits and underscores.
|
|
||||||
fn scandecimalrun(l: *lex) void = {
|
fn scandecimalrun(l: *lex) void = {
|
||||||
for (true) {
|
for (true) {
|
||||||
let c: i32 = lpeek(l, 0u64);
|
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 = {
|
fn scanexp(l: *lex) void = {
|
||||||
let e: i32 = lpeek(l, 0u64);
|
let e: i32 = lpeek(l, 0u64);
|
||||||
if (e != 'e') { if (e != 'E') { return; }; };
|
if (e != 'e') { if (e != 'E') { return; }; };
|
||||||
@@ -752,8 +746,6 @@ fn emitsimple(start: *pos, k: tkind, out: *tok) void = {
|
|||||||
out.col = start.col;
|
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 = {
|
fn setposfrom(out: *tok, p: *pos) void = {
|
||||||
out.file = p.file;
|
out.file = p.file;
|
||||||
out.line = p.line;
|
out.line = p.line;
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
// lib/ww/syntax/parse.ww — port of cmd/wcc/parse.c (entry + plumbing).
|
// 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.
|
|
||||||
//
|
//
|
||||||
// Calling-convention shim: w6c can't yet pass a sub-struct field
|
// 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
|
// (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;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// expectident — consume the current tkind.TK_IDENT and return its text.
|
|
||||||
// Returns the empty str on error (and advances to make progress).
|
// Returns the empty str on error (and advances to make progress).
|
||||||
fn expectident(p: *parser, into: *str) bool = {
|
fn expectident(p: *parser, into: *str) bool = {
|
||||||
if (p.curkind != tkind.TK_IDENT) {
|
if (p.curkind != tkind.TK_IDENT) {
|
||||||
@@ -109,9 +103,7 @@ fn expectident(p: *parser, into: *str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// expectbindname — like expectident but also accepts a bare `_`
|
// On `_`, returns "" so the checker skips scope_define for the binding.
|
||||||
// discard marker. On `_`, returns "" so the checker skips
|
|
||||||
// scope_define for the binding.
|
|
||||||
fn expectbindname(p: *parser, into: *str) bool = {
|
fn expectbindname(p: *parser, into: *str) bool = {
|
||||||
if (p.curkind == tkind.TK_UNDER) {
|
if (p.curkind == tkind.TK_UNDER) {
|
||||||
*into = "";
|
*into = "";
|
||||||
@@ -121,14 +113,7 @@ fn expectbindname(p: *parser, into: *str) bool = {
|
|||||||
return expectident(p, into);
|
return expectident(p, into);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- type expressions ------------------------------------------------
|
// Mirrors aprintf in the C parser; pulled local to avoid a
|
||||||
//
|
|
||||||
// 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
|
|
||||||
// cross-module dependency.
|
// cross-module dependency.
|
||||||
fn joindotted(head: str, tail: str) str = {
|
fn joindotted(head: str, tail: str) str = {
|
||||||
let n: u64 = head.len: u64 + 1u64 + tail.len: u64;
|
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);
|
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 = {
|
fn bprec(k: tkind) i32 = {
|
||||||
if (k == tkind.TK_OR) { return 1; };
|
if (k == tkind.TK_OR) { return 1; };
|
||||||
if (k == tkind.TK_AND) { return 2; };
|
if (k == tkind.TK_AND) { return 2; };
|
||||||
|
|||||||
@@ -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;
|
package syntax;
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ fn parseif(p: *parser) *node = {
|
|||||||
let pf = p.curfile;
|
let pf = p.curfile;
|
||||||
let pl = p.curline;
|
let pl = p.curline;
|
||||||
let pc = p.curcol;
|
let pc = p.curcol;
|
||||||
advance(p); // past `if`
|
advance(p);
|
||||||
expecttok(p, tkind.TK_LPAREN, "expected '(' after if");
|
expecttok(p, tkind.TK_LPAREN, "expected '(' after if");
|
||||||
let n = newnode(nkind.N_IF, pf, pl, pc);
|
let n = newnode(nkind.N_IF, pf, pl, pc);
|
||||||
n.cond = parseexpr(p);
|
n.cond = parseexpr(p);
|
||||||
@@ -138,7 +138,7 @@ fn parsefor(p: *parser) *node = {
|
|||||||
let pf = p.curfile;
|
let pf = p.curfile;
|
||||||
let pl = p.curline;
|
let pl = p.curline;
|
||||||
let pc = p.curcol;
|
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 bare `for { }` — no header at all (cmd/wcc/parse.c:1023;
|
||||||
// Go's for has exactly three forms and this is the empty one).
|
// 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
|
// Range and 3-clause both lead with `let`, so we commit to consuming
|
||||||
// `let` then disambiguate by looking at what follows.
|
// `let` then disambiguate by looking at what follows.
|
||||||
if (p.curkind == tkind.TK_LET) {
|
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) {
|
if (p.curkind == tkind.TK_LPAREN) {
|
||||||
advance(p);
|
advance(p);
|
||||||
let names: *node = nil;
|
let names: *node = nil;
|
||||||
@@ -215,7 +214,7 @@ fn parsefor(p: *parser) *node = {
|
|||||||
let lpf = p.curfile;
|
let lpf = p.curfile;
|
||||||
let lpl = p.curline;
|
let lpl = p.curline;
|
||||||
let lpc = p.curcol;
|
let lpc = p.curcol;
|
||||||
advance(p); // consume IDENT/UNDER
|
advance(p);
|
||||||
|
|
||||||
if (p.curkind == tkind.TK_DOTDOT) {
|
if (p.curkind == tkind.TK_DOTDOT) {
|
||||||
advance(p);
|
advance(p);
|
||||||
@@ -277,7 +276,7 @@ fn parseswitch(p: *parser) *node = {
|
|||||||
let pf = p.curfile;
|
let pf = p.curfile;
|
||||||
let pl = p.curline;
|
let pl = p.curline;
|
||||||
let pc = p.curcol;
|
let pc = p.curcol;
|
||||||
advance(p); // past `switch`
|
advance(p);
|
||||||
expecttok(p, tkind.TK_LPAREN, "expected '(' after switch");
|
expecttok(p, tkind.TK_LPAREN, "expected '(' after switch");
|
||||||
let n = newnode(nkind.N_SWITCH, pf, pl, pc);
|
let n = newnode(nkind.N_SWITCH, pf, pl, pc);
|
||||||
n.lhs = parseexpr(p);
|
n.lhs = parseexpr(p);
|
||||||
@@ -289,7 +288,7 @@ fn parseswitch(p: *parser) *node = {
|
|||||||
let cpf = p.curfile;
|
let cpf = p.curfile;
|
||||||
let cpl = p.curline;
|
let cpl = p.curline;
|
||||||
let cpc = p.curcol;
|
let cpc = p.curcol;
|
||||||
advance(p); // past `case`
|
advance(p);
|
||||||
let cs = newnode(nkind.N_CASE, cpf, cpl, cpc);
|
let cs = newnode(nkind.N_CASE, cpf, cpl, cpc);
|
||||||
let eh: *node = nil;
|
let eh: *node = nil;
|
||||||
let et: *node = nil;
|
let et: *node = nil;
|
||||||
@@ -437,4 +436,3 @@ fn parsestmt(p: *parser) *node = {
|
|||||||
expecttok(p, tkind.TK_SEMI, "expected ';' after expression statement");
|
expecttok(p, tkind.TK_SEMI, "expected ';' after expression statement");
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
// Duplicate definitions in the same scope return nil; the caller
|
||||||
// Plan 9 / Hare flavoured. Duplicate definitions in the same scope
|
// flags the error.
|
||||||
// return nil; the caller flags the error.
|
|
||||||
|
|
||||||
package syntax;
|
package syntax;
|
||||||
|
|
||||||
@@ -103,11 +102,8 @@ export fn scopelookup(s: *scope, name: str) *sym = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// scopelookuptype — find an SK_TYPE entry by name, same-module preferred.
|
// Disambiguates the bare-TNAME vs imported-module-bareword
|
||||||
//
|
// collision: when scopelookup returns the
|
||||||
// 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
|
|
||||||
// SK_USE sym for a leaf that ALSO names a type (a same-name `import X;`
|
// 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
|
// 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
|
// 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;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// scopelookupuselocal — find a same-leaf SK_USE entry within ONE scope.
|
// The dot-lhs twin of
|
||||||
//
|
|
||||||
// Same FNV bucket + hashnext chain as scopelookuplocal, with a
|
|
||||||
// `skind == SK_USE` filter and NO parent walk. The dot-lhs twin of
|
|
||||||
// scopelookuptype: when a `use mod;` and a colliding top-level
|
// scopelookuptype: when a `use mod;` and a colliding top-level
|
||||||
// `fn mod` / `type mod` of the same leaf coexist (random.random,
|
// `fn mod` / `type mod` of the same leaf coexist (random.random,
|
||||||
// fnmatch.fnmatch), the mod-preferring scopelookupprefer returns the
|
// fnmatch.fnmatch), the mod-preferring scopelookupprefer returns the
|
||||||
@@ -198,12 +191,9 @@ export fn scopelookupuselocal(s: *scope, name: str) *sym = {
|
|||||||
return nil;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// scopelookupinmodule — module-filtered chain walk.
|
// When `mod` is empty we fall back to unfiltered scopelookup
|
||||||
//
|
// semantics, so callers that don't care about disambiguation get the
|
||||||
// Same FNV bucket + hashnext chain + parent walk as scopelookup, plus
|
// default.
|
||||||
// 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.
|
|
||||||
//
|
//
|
||||||
// Used by the dot-prefixed type-name lookup in selfhost/cmd/wcc/
|
// 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
|
// 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;
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
// scopelookupprefer — bare-leaf lookup with same-module preference.
|
// Within each scope's bucket: Pass 1 prefers entries whose
|
||||||
//
|
|
||||||
// Walks the same FNV bucket + hashnext chain + parent walk scopelookup
|
|
||||||
// uses. Within each scope's bucket: Pass 1 prefers entries whose
|
|
||||||
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
// `sym.mod` matches `mod`; Pass 2 falls back to the first match
|
||||||
// regardless of mod (same semantics as scopelookup). We only descend
|
// regardless of mod (same semantics as scopelookup). We only descend
|
||||||
// to the parent scope when the current scope has no matching entry at
|
// 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);
|
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
|
// Same insertion as scopedefine, but the duplicate-rejection key is
|
||||||
// (name, mod) rather than name alone. This lets two imports each
|
// (name, mod) rather than name alone. This lets two imports each
|
||||||
// register their own `stream` SK_TYPE in the flat scope, and lets the
|
// register their own `stream` SK_TYPE in the flat scope, and lets the
|
||||||
|
|||||||
@@ -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,
|
// (hashtable scope semantics: define, same-scope duplicate reject,
|
||||||
// kind-preserving lookup, not-found nil). Run with
|
// kind-preserving lookup, not-found nil).
|
||||||
// `ww test -I lib/ww lib/ww/syntax/symtest.ww`.
|
|
||||||
//
|
//
|
||||||
// Migrated from selfhost/test/sym_link.ww (the 990_selfhost link
|
// Migrated from selfhost/test/sym_link.ww (the 990_selfhost link
|
||||||
// probe): the toolchain-link half of that probe is owned by the
|
// probe): the toolchain-link half of that probe is owned by the
|
||||||
// fixture corpus' ww-stage cells, so only the scope behavior rows
|
// fixture corpus' ww-stage cells, so only the scope behavior rows
|
||||||
// survive, as in-language rows. `package main` + bare `import syntax`
|
// survive, as in-language rows.
|
||||||
// mirrors toktest/asttest.
|
|
||||||
|
|
||||||
package syntax_test;
|
package syntax_test;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
// lib/ww/syntax/tok.ww — port of cmd/wcc/tok.c plus the Tkind /
|
// Port of cmd/wcc/tok.c plus the Tkind / Tok / Pos shapes from cmd/wcc/ww.h.
|
||||||
// Tok / Pos shapes from cmd/wcc/ww.h.
|
|
||||||
//
|
//
|
||||||
// Token kind values must stay numerically equal to the C side
|
// Token kind values must stay numerically equal to the C side
|
||||||
// (rule-6 data-shape mirror of cmd/wcc/ww.h). Reordering this list
|
// (rule-6 data-shape mirror of cmd/wcc/ww.h). Reordering this list
|
||||||
// shifts the integers and splits the two frontends.
|
// 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;
|
package syntax;
|
||||||
|
|
||||||
@@ -14,7 +10,6 @@ import os;
|
|||||||
import strconv;
|
import strconv;
|
||||||
import strings;
|
import strings;
|
||||||
|
|
||||||
// ---- tkind ------------------------------------------------------------
|
|
||||||
// Mirror of the C `Tkind` enum in cmd/wcc/ww.h. Numeric values are
|
// Mirror of the C `Tkind` enum in cmd/wcc/ww.h. Numeric values are
|
||||||
// explicit and must stay in sync with the C side.
|
// explicit and must stay in sync with the C side.
|
||||||
|
|
||||||
@@ -125,8 +120,6 @@ export type tkind = enum i32 {
|
|||||||
TK_LAST = 89,
|
TK_LAST = 89,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Pos / Tok --------------------------------------------------------
|
|
||||||
//
|
|
||||||
// `pos` is used at error-reporting boundaries; we always pass it via
|
// `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
|
// *pos so the value never gets struct-copied (w6c can't yet copy a
|
||||||
// 24-byte struct).
|
// 24-byte struct).
|
||||||
@@ -152,8 +145,6 @@ export type tok = struct {
|
|||||||
tsuffix: str, // typed numeric literal suffix or empty
|
tsuffix: str, // typed numeric literal suffix or empty
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- keyword lookup ---------------------------------------------------
|
|
||||||
|
|
||||||
// keep alphabetised, so kwlookup is easy to read — mirrors the C twin
|
// 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-
|
// 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
|
// 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,
|
tkind.TK_VOID, tkind.TK_YIELD,
|
||||||
];
|
];
|
||||||
|
|
||||||
// kwlookup — returns the matching TK_* keyword kind for a byte run,
|
// Linear scan over the table, matching cmd/wcc/tok.c:kwlookup (N=30, no hash).
|
||||||
// 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).
|
|
||||||
export fn kwlookup(p: *u8, n: i32) tkind = {
|
export fn kwlookup(p: *u8, n: i32) tkind = {
|
||||||
let cand: str;
|
let cand: str;
|
||||||
cand.ptr = p;
|
cand.ptr = p;
|
||||||
@@ -197,10 +186,7 @@ export fn kwlookup(p: *u8, n: i32) tkind = {
|
|||||||
return tkind.TK_NONE;
|
return tkind.TK_NONE;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- tokname ----------------------------------------------------------
|
// Matches the C tokname()'s output exactly so wwdump output diffs cleanly.
|
||||||
//
|
|
||||||
// Returns the canonical printable spelling for a token kind. Matches
|
|
||||||
// the C tokname()'s output exactly so wwdump output diffs cleanly.
|
|
||||||
|
|
||||||
export fn tokname(k: tkind) str = {
|
export fn tokname(k: tkind) str = {
|
||||||
switch (k) {
|
switch (k) {
|
||||||
@@ -306,8 +292,6 @@ export fn tokname(k: tkind) str = {
|
|||||||
return "<?>";
|
return "<?>";
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- writer for tokprint ----------------------------------------------
|
|
||||||
//
|
|
||||||
// fputq mirrors cmd/wcc/tok.c:fputq — quote the string with C-style
|
// fputq mirrors cmd/wcc/tok.c:fputq — quote the string with C-style
|
||||||
// escapes for \, ", \n, \t, \r and \xNN for other non-printables.
|
// 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, '"');
|
fputcbyte(fd, '"');
|
||||||
};
|
};
|
||||||
|
|
||||||
// tokprint — write one token line to fd. Format must match
|
// Format must match cmd/wcc/tok.c:tokprint() byte-for-byte: that's
|
||||||
// cmd/wcc/tok.c:tokprint() byte-for-byte: that's the diff anchor.
|
// the diff anchor.
|
||||||
// "<file>:<line>:<col> <kindname>[ <value>]\n"
|
// "<file>:<line>:<col> <kindname>[ <value>]\n"
|
||||||
//
|
//
|
||||||
// Takes `t` by pointer because w6c can't yet pass a >16-byte struct
|
// Takes `t` by pointer because w6c can't yet pass a >16-byte struct
|
||||||
|
|||||||
@@ -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
|
// (struct fold S1) and [[tokprint]]/fputq (struct fold S9, the
|
||||||
// if-ladder → switch folds in tok.ww).
|
// 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
|
// tokname is checked against every tkind value (the full ladder the
|
||||||
// switch replaced, plus the unknown-kind fallback); kwlookup is
|
// 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
|
// A failing row aborts via the assert/abort builtin (task #5 @test
|
||||||
// conversion); per-row exit-code pinpoint is intentionally dropped (the
|
// conversion); per-row exit-code pinpoint is intentionally dropped (the
|
||||||
// abort reports the file, not the row; drew-t2-conversion-spec sec.5).
|
// 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;
|
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
|
// The fd is RDWR; we lseek to 0
|
||||||
// back, and assert they equal `want`. The fd is RDWR; we lseek to 0
|
|
||||||
// before each write so earlier (possibly longer) content past want.len
|
// before each write so earlier (possibly longer) content past want.len
|
||||||
// is irrelevant — only want.len bytes from offset 0 are compared.
|
// is irrelevant — only want.len bytes from offset 0 are compared.
|
||||||
fn checkprint(fd: i32, t: *tok, want: str) void = {
|
fn checkprint(fd: i32, t: *tok, want: str) void = {
|
||||||
|
|||||||
@@ -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
|
// the primitive types (tyvoid, tyi32, …); ww doesn't have writable
|
||||||
// global storage yet, so we bundle the primitives into a `tctx` that
|
// global storage yet, so we bundle the primitives into a `tctx` that
|
||||||
// the checker passes around explicitly. typesinit fills the tctx
|
// the checker passes around explicitly. typesinit fills the tctx
|
||||||
@@ -10,11 +10,6 @@ package syntax;
|
|||||||
|
|
||||||
import os;
|
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
|
// Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values
|
||||||
// are explicit and must stay in sync — the selfhost selfcheck and
|
// are explicit and must stay in sync — the selfhost selfcheck and
|
||||||
// typed-AST printers depend on matching numeric layout.
|
// 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.
|
// fabricate a 0-byte slot. Value == U64_MAX.
|
||||||
def SIZE_UNDEFINED: u64 = 18446744073709551615;
|
def SIZE_UNDEFINED: u64 = 18446744073709551615;
|
||||||
|
|
||||||
// ---- tinfo / tfield / tparam -----------------------------------------
|
|
||||||
|
|
||||||
export type tfield = struct {
|
export type tfield = struct {
|
||||||
name: str,
|
name: str,
|
||||||
type_: *tinfo,
|
type_: *tinfo,
|
||||||
@@ -173,8 +166,6 @@ export type tinfocacheent = struct {
|
|||||||
// scaled up — sym's 16 would give ~340-deep chains here.
|
// scaled up — sym's 16 would give ~340-deep chains here.
|
||||||
def NBUCKETS_TINFO: u64 = 8192u64;
|
def NBUCKETS_TINFO: u64 = 8192u64;
|
||||||
|
|
||||||
// ---- tctx — the box of primitive types -------------------------------
|
|
||||||
|
|
||||||
export type tctx = struct {
|
export type tctx = struct {
|
||||||
tyvoid: *tinfo,
|
tyvoid: *tinfo,
|
||||||
tybool: *tinfo,
|
tybool: *tinfo,
|
||||||
@@ -206,8 +197,6 @@ export type tctx = struct {
|
|||||||
tinfobuckets: **tinfocacheent, // length NBUCKETS_TINFO; node-ptr hash index
|
tinfobuckets: **tinfocacheent, // length NBUCKETS_TINFO; node-ptr hash index
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- constructors -----------------------------------------------------
|
|
||||||
|
|
||||||
export fn newtype(k: tykind) *tinfo = {
|
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})!;
|
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;
|
return t;
|
||||||
@@ -357,8 +346,6 @@ export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
|
|||||||
c.tinfobuckets[bi] = e;
|
c.tinfobuckets[bi] = e;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- predicates -------------------------------------------------------
|
|
||||||
|
|
||||||
export fn typeisint(t: *tinfo) bool = {
|
export fn typeisint(t: *tinfo) bool = {
|
||||||
if (t == nil) { return false; };
|
if (t == nil) { return false; };
|
||||||
let k: tykind = t.kind;
|
let k: tykind = t.kind;
|
||||||
|
|||||||
Reference in New Issue
Block a user