diff --git a/lib/path/path.ww b/lib/path/path.ww index b1f1c2fe..4fdeec50 100644 --- a/lib/path/path.ww +++ b/lib/path/path.ww @@ -1,8 +1,9 @@ // path — filesystem path manipulation. UTF-8 paths, '/' separator. // Mirrors Hare's path:: surface (ref/hare/path/). This is the // path::buffer stack-buffer port (c2-stack subset: error/posix defs + -// buffer + isroot + appendlit + appendnorm + push + string + set). -// init/abs/dirname/basename and the rest land in later folds. +// buffer + isroot + appendlit + appendnorm + push + string + set) plus +// the c3-posix subset (dirname + basename, ref/hare/path/posix.ha). +// init/abs and the rest land in later folds. package path; @@ -137,3 +138,51 @@ export fn set(buf: *buffer, items: str...) (str | error) = { buf.end = 0; return push(buf, items...); }; + +// ---- POSIX jail (ref/hare/path/posix.ha) ------------------------------ +// POSIX-compliant dirname/basename. They do NOT normalize the input and +// operate on plain str (no buffer), so they sit apart from the stack +// paradigm above — same POSIX-complaint as posix.ha:7-11. + +// ref/hare/path/posix.ha:17. Does *not* normalize; the return is either a +// literal ("."/sepstr) or a borrowed view of the input. Div: toutf8 stays +// strings.toutf8; len(path)→path.len; fromutf8_unsafe→strings.frombytes +// (rule-9 carve-out); rindex z:size→i32; reslice `[..z]`→`[0:z]` (div #4). +// Div: Hare shadows the param (`let path = strings::toutf8(path)`); ww's +// new `let` binding is in scope for its OWN initializer, so the shadow +// reads the uninitialized local — work var renamed `p` (cstage self-init +// scope bug, #152). +export fn dirname(path: str) str = { + let p: []u8 = strings.toutf8(path); + if (p.len == 0) { return "."; }; + + p = bytes.rtrim(p, SEP); + if (p.len == 0) { return sepstr; }; + + match (bytes.rindex(p, SEP)) { + case void => { return "."; }; + case let z: i32 => { p = p[0:z]; }; + }; + p = bytes.rtrim(p, SEP); + + if (p.len == 0) { return sepstr; }; + return strings.frombytes(p); +}; + +// ref/hare/path/posix.ha:37. Does *not* normalize; the return is a +// borrowed view of the input. Div: as dirname (incl. param-shadow rename +// to `p`, #152); reslice `[z+1..]`→`[z+1:]` (div #4); empty-match no-op +// arm spelled `case void => void;` (bytes.ww:130 precedent). +export fn basename(path: str) str = { + let p: []u8 = strings.toutf8(path); + if (p.len == 0) { return "."; }; + + p = bytes.rtrim(p, SEP); + if (p.len == 0) { return sepstr; }; + + match (bytes.rindex(p, SEP)) { + case void => void; + case let z: i32 => { p = p[z+1:]; }; + }; + return strings.frombytes(p); +}; diff --git a/lib/path/pathtest.ww b/lib/path/pathtest.ww index 91d338c1..905d6bfc 100644 --- a/lib/path/pathtest.ww +++ b/lib/path/pathtest.ww @@ -115,9 +115,43 @@ fn fail() void = { os.exit(signalled + 10); }; if (string(&buf) != "foo") { fail(); }; }; +// ---- dirname() / basename() ------------------------------------------- +// ref/hare/path/posix.ha:51-71. Hare seeds each row through _local/local() +// for cross-platform SEP; on ww (Linux, SEP='/') that is identity, so the +// raw str literals stand in directly (local/_local defer to c3). POSIX +// dirname/basename do NOT normalize the input (posix.ha:14-15,35), so no +// seeding is needed. Parallel `[N]str` rows (cgen chained-field gap, as +// above). Rows whose expected is a borrowed VIEW (not a "."/"/" literal) +// compare a computed str to a literal — cstage-correct; wwstage mis- +// compares via #146 (str== over a frombytes result), and this @test is +// cstage-gated, so those rows are #146-deferred on wwstage only. + +@test fn dirname_basename_cases() void = { + let inputs: [10]str; + let wantdir: [10]str; + let wantbase: [10]str; + inputs[0]="usr"; wantdir[0]="."; wantbase[0]="usr"; + inputs[1]="usr/"; wantdir[1]="."; wantbase[1]="usr"; + inputs[2]=""; wantdir[2]="."; wantbase[2]="."; + inputs[3]="/"; wantdir[3]="/"; wantbase[3]="/"; + inputs[4]="//"; wantdir[4]="/"; wantbase[4]="/"; // impl-defined + inputs[5]="///"; wantdir[5]="/"; wantbase[5]="/"; + inputs[6]="/usr/"; wantdir[6]="/"; wantbase[6]="usr"; + inputs[7]="/usr/lib"; wantdir[7]="/usr"; wantbase[7]="lib"; + inputs[8]="//usr//lib//"; wantdir[8]="//usr"; wantbase[8]="lib"; + inputs[9]="/home//dwc//test"; wantdir[9]="/home//dwc"; wantbase[9]="test"; + let i: i32 = 0; + for (i < 10) { + if (dirname(inputs[i]) != wantdir[i]) { fail(); }; + if (basename(inputs[i]) != wantbase[i]) { fail(); }; + i += 1; + }; +}; + export fn main() i32 = { signalled = 1; push_cases(); signalled = 2; isroot_cases(); signalled = 3; string_cases(); + signalled = 4; dirname_basename_cases(); return 0; };