Files
ww/lib/path/path.ww
Hojun-Cho 16f47da916 lib/path: #138 c2-stack buffer port — buffer/push/appendnorm/appendlit/string/isroot/set + dot/dotdot, cstage @test
Faithful realignment of lib/path to Hare's buffer-centric API
(ref/hare/path/{stack,buffer}.ha). Replaces the old str-only path.ww
wholesale (zero consumers). Scope = stack-core: init() deferred (returns
~4KB (buffer|error), rides the #40 arc / #147); abs/dirname/basename land
in c3; extension/join dropped.

appendlit uses the #145 slice-copy-assign arm (buf.buf[lo:hi]=bs), no hand
loop. dot/dotdot are faithful module-global []u8 (D2 #148). MAX =
os.PATH_MAX-1. Divergences (size->i32 indices, frombytes, module-global
consts) cited inline.

Tests: lib/path/pathtest.ww (table-driven), wired at test/wcc/989_path_run.c;
push rows mirror stack.ha:107-111 verbatim incl the restored "/d"
intermediate. Slot 989 (overflow bucket) since 970 is taken.

C-first: cstage @test green; wwstage byte-id deferred to the #125 batch.
path classified M_WWREJECT in 989_lib_byteid (w6c_ww rejects the module-
global slice consts = #120/#29; + the #148 twin #151) — self-graduates back
to M_ID the day wwstage accepts. path moved off the 900_stdlib standalone
list (import-dependent: os.PATH_MAX def-dim + match over imported error
types; coverage at 989_path_run), per the bytes/fmt/os precedent.

Gate: all 324 passed, byte-id 990-997 green, w6c/w6c_ww unchanged
(lib-only, non-embedded).
2026-06-08 11:25:11 +09:00

140 lines
4.5 KiB
Plaintext

// 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.
package path;
import bytes;
import strings;
import os;
// ref/hare/path/error.ha:6-12. The 3 !void singletons are
// structurally identical; cstage discriminates them correctly
// (#142 is a wwstage-only flatvariantidxt ambiguity, rides #125).
export type cant_extend = !void;
export type too_long = !void;
export type not_prefix = !void;
export type error = !(cant_extend | too_long | not_prefix);
// ref/hare/path/error.ha:15-24.
export fn strerror(e: error) str = {
match (e) {
case cant_extend => return "Can't add extension (filename is root or all dots)";
case too_long => return "Path buffer overflow";
case not_prefix => return "Prefix not present";
};
};
// ref/hare/path/+linux.ha:7.
export def SEP: u8 = '/';
// ref/hare/path/+linux.ha:9. No c2 use; c3 abs/isroot-str consume it.
const sepstr: str = "/";
// ref/hare/path/+linux.ha:18 (sys::PATH_MAX-1). Div: route through
// os.PATH_MAX (lib/os/os.ww:84, i32=4096) → MAX=4095; const-folds as
// the buffer array dim (c1 #141). No hardcoded literal (rule-13).
export def MAX: i32 = os.PATH_MAX - 1;
// ref/hare/path/buffer.ha:6-9. Div: end size→i32 (lib/CLAUDE.md
// str.len:i32; bytes.index→(i32|void)).
export type buffer = struct {
buf: [MAX]u8,
end: i32,
};
// ref/hare/path/stack.ha:30-31. Faithful module-global []u8 (D2 #148
// fixes the by-value-arg garbage header) — no [N]u8, no copy-loop.
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/stack.ha:63-74.
fn appendlit(buf: *buffer, bs: []u8) (i32 | error) = {
let newend: i32 = buf.end;
if (buf.end == 0 || isroot(buf)) {
if (MAX < buf.end + bs.len) {
// cstage rejects bare `return too_long` into the nested
// (i32|error); two-step widen (io precedent stream.ww:56-58).
let e: error = too_long; return e;
};
} else {
if (MAX < buf.end + bs.len + 1) {
let e: error = too_long; return e;
};
buf.buf[buf.end] = SEP;
newend += 1;
};
// Div: Hare `buf.buf[newend..newend+len(bs)] = bs` in colon
// spelling — the #145 slice-copy-assign LHS arm (stack.ha:72).
buf.buf[newend:newend+bs.len] = bs;
return newend + bs.len;
};
// ref/hare/path/stack.ha:43-59. Div: `..` reslice→colon; Hare if-expr
// → if-statement; `0z` yield → `0i32` (match arms must agree, bare 0
// is untyped_int).
fn appendnorm(buf: *buffer, seg: []u8) (i32 | error) = {
if (seg.len == 0 || bytes.equal(dot, seg)) { return buf.end; };
if (bytes.equal(dotdot, seg)) {
if (isroot(buf)) { return buf.end; };
let isep: i32 = match (bytes.rindex(buf.buf[0:buf.end], SEP)) {
case void => yield 0i32;
case let i: i32 => yield i + 1;
};
if (buf.end == 0 || bytes.equal(buf.buf[isep:buf.end], dotdot)) {
return appendlit(buf, dotdot)?;
} else {
if (isep <= 1) { return isep; };
return isep - 1;
};
} else {
return appendlit(buf, seg)?;
};
};
// ref/hare/path/buffer.ha:28-31. Div: fromutf8_unsafe→strings.frombytes
// (rule-9 carve-out); `..` reslice→colon.
export fn string(buf: *buffer) str = {
if (buf.end == 0) { return "."; };
return strings.frombytes(buf.buf[0:buf.end]);
};
// ref/hare/path/stack.ha:9-28. Div: `..` reslice→colon; len(x)→x.len.
export fn push(buf: *buffer, items: str...) (str | error) = {
for (let item .. items) {
let elem: []u8 = strings.toutf8(item);
// Hare's `for(true) match{...}` (stack.ha:13-25) breaks out of
// the void arm and re-loops the j-arm; ww spells the loop step
// as explicit `continue` (j-arm) + `break` (after match).
for (true) {
match (bytes.index(elem, SEP)) {
case void => { buf.end = appendnorm(buf, elem)?; };
case let j: i32 => {
if (j == 0 && buf.end == 0) {
buf.buf[0] = SEP; buf.end = 1;
} else {
buf.end = appendnorm(buf, elem[0:j])?;
};
elem = elem[j+1:];
continue;
};
};
break;
};
};
return string(buf);
};
// ref/hare/path/buffer.ha:20-23. (str|error) ≤32B return → in c2-stack.
export fn set(buf: *buffer, items: str...) (str | error) = {
buf.end = 0;
return push(buf, items...);
};