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:
@@ -49,11 +49,24 @@ export type buffer = struct {
|
||||
const dot: []u8 = ['.'];
|
||||
const dotdot: []u8 = ['.', '.'];
|
||||
|
||||
// ref/hare/path/buffer.ha:44-51. c3 WIDENS to isroot(path:(*buffer|str))
|
||||
// alongside abs; c2 needs only the *buffer arm (appendnorm/appendlit
|
||||
// callers).
|
||||
export fn isroot(buf: *buffer) bool = {
|
||||
return buf.end == 1 && buf.buf[0] == SEP;
|
||||
// ref/hare/path/buffer.ha:34-41. str-arm = str-prefix (vs const global,
|
||||
// correct only post-#154); buffer-arm = byte. Hare `0 < buf.end` order kept.
|
||||
export fn abs(path: (*buffer | str)) bool = {
|
||||
match (path) {
|
||||
case let p: str => { return strings.hasprefix(p, sepstr); };
|
||||
case let b: *buffer => { return 0 < b.end && b.buf[0] == SEP; };
|
||||
};
|
||||
};
|
||||
|
||||
// ref/hare/path/buffer.ha:44-51. WIDENED from the c2 *buffer-only body to
|
||||
// the (*buffer | str) union (str-arm = str==, post-#154 correct). The
|
||||
// appendnorm/appendlit callers pass a concrete *buffer that ptr-ident
|
||||
// arg-widens into the union (#55 leg).
|
||||
export fn isroot(path: (*buffer | str)) bool = {
|
||||
match (path) {
|
||||
case let p: str => { return p == sepstr; };
|
||||
case let b: *buffer => { return b.end == 1 && b.buf[0] == SEP; };
|
||||
};
|
||||
};
|
||||
|
||||
// ref/hare/path/stack.ha:63-74.
|
||||
@@ -139,6 +152,79 @@ export fn set(buf: *buffer, items: str...) (str | error) = {
|
||||
return push(buf, items...);
|
||||
};
|
||||
|
||||
// ---- c3 buffer-ops (ref/hare/path/buffer.ha, stack.ha) ----------------
|
||||
|
||||
// lifts Hare's fn-`static let buf` (buffer.ha:53-56): statically allocated,
|
||||
// overwritten on the next local() call. strconv *tos-buf precedent.
|
||||
let localbuf: [MAX]u8 = [0...];
|
||||
|
||||
// ref/hare/path/buffer.ha:55-77. Div: Hare `_local` returns (str|nomem) —
|
||||
// ww has no nomem axis, so the MAX-guard IS the loud bound (→ too_long);
|
||||
// `_local` folded inline (local is its only caller). fromutf8→frombytes
|
||||
// (rule-9 carve-out); `..` reslice→colon; `0z`→`0i32` (div #1,#8).
|
||||
export fn local(path: str) (str | too_long) = {
|
||||
let bs: []u8 = strings.toutf8(path);
|
||||
if (MAX < bs.len) { let e: too_long = too_long; return e; };
|
||||
let n: i32 = 0;
|
||||
for (let b .. bs) {
|
||||
// no ww static-append → index-write (regex.ww:1177 precedent:
|
||||
// static append→plain; index-write keeps the static-view contract)
|
||||
if (b == '/') { localbuf[n] = SEP; } else { localbuf[n] = b; };
|
||||
n += 1;
|
||||
};
|
||||
return strings.frombytes(localbuf[0:n]);
|
||||
};
|
||||
|
||||
// ref/hare/path/stack.ha:133-145. Helper for pop/peek: returns (new end,
|
||||
// result). Over-cap tuple SRET (i32 .0 + (str|void) .1 = 40B > 32B, #22b).
|
||||
// Div: (size,…)→(i32,…) (div #1); `0z`/`1z`→`0i32`/`1`; `..`→`:`;
|
||||
// fromutf8→frombytes; Hare if-expr → if-statement (div #7,#8,#4,#2).
|
||||
fn split(buf: *buffer) (i32, (str | void)) = {
|
||||
if (buf.end == 0 || isroot(buf)) {
|
||||
let e: i32 = buf.end;
|
||||
let r: (str | void) = void; // #22b: bind-then-return
|
||||
return (e, r);
|
||||
};
|
||||
match (bytes.rindex(buf.buf[0:buf.end], SEP)) {
|
||||
case void => {
|
||||
let e: i32 = 0i32;
|
||||
let r: (str | void) = strings.frombytes(buf.buf[0:buf.end]); // #22b: bind-then-return
|
||||
return (e, r);
|
||||
};
|
||||
case let i: i32 => {
|
||||
let e: i32 = i;
|
||||
if (i == 0) { e = 1; };
|
||||
let r: (str | void) = strings.frombytes(buf.buf[i+1:buf.end]); // #22b: bind-then-return
|
||||
return (e, r);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// ref/hare/path/stack.ha:125-131. Remove and return the final segment;
|
||||
// void if the path is empty or the root dir.
|
||||
export fn pop(buf: *buffer) (str | void) = {
|
||||
const (end, res) = split(buf);
|
||||
buf.end = end;
|
||||
return res;
|
||||
};
|
||||
|
||||
// ref/hare/path/stack.ha:121-123. Examine the final segment without
|
||||
// mutating the buffer; void if empty or root.
|
||||
export fn peek(buf: *buffer) (str | void) = {
|
||||
let t = split(buf); // #48: bind split to local (not .1 off a CALL)
|
||||
return t.1;
|
||||
};
|
||||
|
||||
// ref/hare/path/stack.ha:172-180. Parent directory; root → root. Hare's
|
||||
// doc says "without modifying the buffer" but the BODY mutates via
|
||||
// appendnorm — ported verbatim, not "fixed". Div: drop `const` ptr-qual;
|
||||
// fromutf8→frombytes; `..`→`:` (div #2,#4).
|
||||
export fn parent(buf: *buffer) (str | error) = {
|
||||
let newend: i32 = appendnorm(buf, dotdot)?;
|
||||
if (newend == 0) { return "."; };
|
||||
return strings.frombytes(buf.buf[0:newend]);
|
||||
};
|
||||
|
||||
// ---- POSIX jail (ref/hare/path/posix.ha) ------------------------------
|
||||
// POSIX-compliant dirname/basename. They do NOT normalize the input and
|
||||
// operate on plain str (no buffer), so they sit apart from the stack
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user