lib/path: #153 c3 buffer-ops — abs/isroot-widen/local/parent/split/pop/peek faithful port, table-driven @test (cstage)

Faithful ref/hare/path/buffer.ha port of the buffer-mutating ops. isroot
widened *buffer->(*buffer|str) (existing callers ptr-ident-widen); abs str-arm
strings.hasprefix(p,sepstr) (rides #154); local lifts Hare's `static let buf`
to a module-private [MAX]u8 index-write (no ww static-append, regex.ww:1177).

Three documented respells (never silent): split binds each (str|void) elem to
an ident before return (#22b); peek binds split() to a local (#48); local's
index-write. parent via appendnorm + frombytes.

Tests mirror Hare's pop() @test 1:1 (stack.ha:147) + table-driven
abs/isroot/local/parent rows off a local buffer. cstage-first; wwstage str-arms
ride deferred #146 (path stays M_WWREJECT). lib/path non-embedded (no
combined.ww regen). Completes the c3 stage of the path arc.
This commit is contained in:
2026-06-08 13:37:23 +09:00
parent 3f6b68cbf2
commit ba13940b96
2 changed files with 221 additions and 7 deletions

View File

@@ -79,9 +79,40 @@ fn fail() void = { os.exit(signalled + 10); };
if (push(&buf, "/foo")! != "/foo") { fail(); };
};
// ---- abs() ------------------------------------------------------------
// ref/hare/path/buffer.ha:34-41. str literals widen into the (*buffer|str)
// union arg (str-arm = hasprefix vs sepstr); buffer-arm = byte. Parallel
// [N]str inputs + [N]i32 wantbool (no [N]bool in the stack; cgen-field gap).
@test fn abs_cases() void = {
let ins: [4]str;
let want: [4]i32;
ins[0]="/"; want[0]=1;
ins[1]="/foo"; want[1]=1;
ins[2]="foo"; want[2]=0;
ins[3]=""; want[3]=0;
let i: i32 = 0;
for (i < 4) {
let got: i32 = 0;
if (abs(ins[i])) { got = 1; };
if (got != want[i]) { fail(); };
i += 1;
};
// buffer-arm: &buf ptr-ident-widens into the union.
let buf = buffer { ... };
if (abs(&buf)) { fail(); }; // empty → false
if (push(&buf, "/foo")! != "/foo") { fail(); };
if (!abs(&buf)) { fail(); }; // "/foo" → true
buf.end = 0;
if (push(&buf, "foo")! != "foo") { fail(); };
if (abs(&buf)) { fail(); }; // "foo" → false
};
// ---- isroot() ---------------------------------------------------------
// ref/hare/path/buffer.ha:44-51 (*buffer arm). Heterogeneous seeds
// (manual root seed vs push) → inline rather than a uniform table.
// ref/hare/path/buffer.ha:44-51. Both arms: str (== sepstr) and *buffer
// (byte+int). Heterogeneous buffer seeds (manual root seed vs push) →
// inline; str-arm rows table-driven.
@test fn isroot_cases() void = {
let buf = buffer { ... };
@@ -102,6 +133,21 @@ fn fail() void = { os.exit(signalled + 10); };
buf.buf[0] = SEP; buf.end = 1;
if (push(&buf, "foo")! != "/foo") { fail(); };
if (isroot(&buf)) { fail(); };
// str-arm (buffer.ha:47): only the bare separator is root.
let ins: [4]str;
let want: [4]i32;
ins[0]="/"; want[0]=1;
ins[1]="/foo"; want[1]=0;
ins[2]="foo"; want[2]=0;
ins[3]=""; want[3]=0;
let i: i32 = 0;
for (i < 4) {
let got: i32 = 0;
if (isroot(ins[i])) { got = 1; };
if (got != want[i]) { fail(); };
i += 1;
};
};
// ---- string() ---------------------------------------------------------
@@ -148,10 +194,92 @@ fn fail() void = { os.exit(signalled + 10); };
};
};
// ---- local() ----------------------------------------------------------
// ref/hare/path/buffer.ha:55-77. Rewrites '/'→SEP into a static buffer;
// on Linux (SEP=='/') that is identity. The returned view borrows the
// module-private localbuf, so compare each row before the next call.
@test fn local_cases() void = {
let ins: [4]str;
let want: [4]str;
ins[0]="a/b"; want[0]="a/b";
ins[1]="/"; want[1]="/";
ins[2]="foo"; want[2]="foo";
ins[3]=""; want[3]="";
let i: i32 = 0;
for (i < 4) {
if (local(ins[i])! != want[i]) { fail(); };
i += 1;
};
};
// ---- parent() ---------------------------------------------------------
// ref/hare/path/stack.ha:172-180. Parent dir; root → root; empty → "..".
// Each row reseeds a reset buffer (parent goes through appendnorm). push's
// normalized echo is dropped so the empty-buffer row ("" pushes to ".",
// not "") can share the table.
@test fn parent_cases() void = {
let buf = buffer { ... };
let ins: [5]str;
let want: [5]str;
ins[0]="/usr/lib"; want[0]="/usr";
ins[1]="foo"; want[1]=".";
ins[2]="/"; want[2]="/";
ins[3]="a/b/c"; want[3]="a/b";
ins[4]=""; want[4]=".."; // empty buffer: appendnorm(dotdot) → ".."
let i: i32 = 0;
for (i < 5) {
buf.end = 0;
push(&buf, ins[i])!;
if (parent(&buf)! != want[i]) { fail(); };
i += 1;
};
};
// ---- split() / pop() / peek() -----------------------------------------
// ref/hare/path/stack.ha:147-170 (@test pop). split is the helper, covered
// through pop/peek. Sequential off ONE buffer, reset buf.end=0 per case.
// The peek-then-string-unchanged assert is load-bearing: it pins that the
// #48 respelling didn't smuggle in pop's buf.end mutation.
@test fn popsplit_cases() void = {
let buf = buffer { ... };
// empty
if (!(pop(&buf) is void)) { fail(); };
if (string(&buf) != ".") { fail(); };
// root dir
buf.end = 0;
if (push(&buf, "/")! != "/") { fail(); };
if (!(pop(&buf) is void)) { fail(); };
if (string(&buf) != "/") { fail(); };
// relative file — peek is NON-mutating
buf.end = 0;
if (push(&buf, "foo")! != "foo") { fail(); };
if (peek(&buf) as str != "foo") { fail(); };
if (string(&buf) != "foo") { fail(); };
if (pop(&buf) as str != "foo") { fail(); };
if (string(&buf) != ".") { fail(); };
// absolute file
buf.end = 0;
if (push(&buf, "/foo")! != "/foo") { fail(); };
if (peek(&buf) as str != "foo") { fail(); };
if (pop(&buf) as str != "foo") { fail(); };
if (string(&buf) != "/") { fail(); };
};
export fn main() i32 = {
signalled = 1; push_cases();
signalled = 2; isroot_cases();
signalled = 3; string_cases();
signalled = 4; dirname_basename_cases();
signalled = 5; abs_cases();
signalled = 6; local_cases();
signalled = 7; parent_cases();
signalled = 8; popsplit_cases();
return 0;
};