lib: add missing Hare-stdlib functions (ascii/bytes/strings/path/endian)
ascii: valid, validstr, ispunct, isprint, iscntrl, isgraph, isblank, strcasecmp. bytes: hasprefix, hassuffix, rindex, rindexbyte, contains, reverse, zero. strings: rindex, sub, trimprefix, trimsuffix, ltrimbyte, rtrimbyte, trimbyte. The byte-set trim is a single-byte subset of Hare's `trim(input, exclude: rune...)`; no variadic ABI yet. path: dirname, basename, extension, join. Owned-str returns where the result isn't a borrowed view of the input (join). endian: full Hare table — be/le get/put for u16/u32/u64 plus the network-order htonu/ntohu pair extended to 32/64. lib/CLAUDE.md rewritten to reflect the post-graduation policy (tagged-union returns, owned-str returns, plan9 names, documented deviations for non-graduating modules). Makefile picks up lib/ascii and lib/fmt as wwdump_ww / w6c_ww deps so lib-only edits regenerate the affected binaries.
This commit is contained in:
4
Makefile
4
Makefile
@@ -113,7 +113,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \
|
||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||
selfhost/cmd/wcc/cgendecl.ww \
|
||||
lib/os/os.ww lib/strconv/strconv.ww \
|
||||
lib/os/os.ww lib/strconv/strconv.ww lib/ascii/ascii.ww lib/fmt/fmt.ww \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
cd $(BIN) && ./ww build \
|
||||
@@ -134,7 +134,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \
|
||||
selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \
|
||||
selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \
|
||||
selfhost/cmd/wcc/cgendecl.ww \
|
||||
lib/os/os.ww lib/strconv/strconv.ww \
|
||||
lib/os/os.ww lib/strconv/strconv.ww lib/ascii/ascii.ww lib/fmt/fmt.ww \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
cd $(BIN) && ./ww build \
|
||||
|
||||
@@ -1,35 +1,63 @@
|
||||
lib/ — Hare-shaped standard library, deliberately a subset.
|
||||
lib/ — Hare-shaped standard library.
|
||||
|
||||
Scope: every `lib/*` directory except `lib/ww/`, which is the compiler
|
||||
frontend port and has its own rules (see `lib/ww/CLAUDE.md`).
|
||||
|
||||
Names mirror Hare. Before adding a function, find its counterpart in
|
||||
`ref/hare/<module>/` and copy the name verbatim (`strings.compare`,
|
||||
`bytes.index`, `strconv.stoi64`, `io.read`, `path.init`). Don't
|
||||
invent. Don't shorten. Don't reorder parameters.
|
||||
`ref/hare/<module>/` and copy the name — drop Hare's underscores per
|
||||
plan 9 style (`trim_prefix` → `trimprefix`, `next_token` → `nexttoken`).
|
||||
Don't invent. Don't shorten further. Don't reorder parameters.
|
||||
|
||||
Signatures are intentionally a Plan 9 / pre-tagged-union subset,
|
||||
because the wwstage compiler doesn't yet have ergonomic tagged-union
|
||||
returns or full variadic ABI:
|
||||
Signatures mirror Hare too, modulo:
|
||||
|
||||
- `i32` with `-1` sentinel stands in for Hare's `(size | void)` and
|
||||
`(size | io::EOF)`. Example: `bytes.indexbyte(s, c) i32` returning
|
||||
`-1` for not-found.
|
||||
- Plain `str` errors stand in for tagged error types (`invalid`,
|
||||
`overflow`, `io::error`). Example: `strconv.parse64(s) (i64 | str)`
|
||||
where the `str` is the message Hare would have tagged.
|
||||
- `lib/fmt` is print/println-only — printf-family waits on a variadic
|
||||
ABI.
|
||||
- Allocation-free at the boundary: where Hare returns a freshly-
|
||||
allocated slice, take a caller-supplied buffer. No GC, and the `rt`
|
||||
allocator is not a stable public surface yet.
|
||||
- Tagged-union returns are spelled with the ww `!` error tag where
|
||||
Hare uses `!void` / `!T`, and indices use the underlying length
|
||||
type (`i32` today, since `str.len: i32`). Example:
|
||||
`strconv.stoi64(s: str, b: i32) (i64 | invalid | overflow)` —
|
||||
same shape as Hare's; the base parameter is plain `i32` rather
|
||||
than a `base` enum because cross-module `mod.enumtype.VALUE`
|
||||
chains miscompile in the cstage cgen. `strconv` exports
|
||||
`def DEC: i32 = 10;` etc. so callers say `strconv.DEC` and the
|
||||
Sdef path lowers to an immediate.
|
||||
|
||||
- Owned-`str` returns. Where Hare returns `const str` into a
|
||||
thread-local static buffer (`strconv.i64tos`, `strings.dup`,
|
||||
`strings.concat`), ww allocates per call and the caller frees
|
||||
via `os.free(r.ptr, r.len: u64)`. Mutating a module-level `*u8`
|
||||
doesn't yet round-trip through the wwstage cgen, so the static-
|
||||
buffer shape isn't expressible today.
|
||||
|
||||
- Variadic ABI doesn't land yet, so callers that Hare writes as
|
||||
`fmt::println(42)` are spelled `fmt.println(strconv.i64tos(42,
|
||||
strconv.DEC))` for now. `lib/fmt` is intentionally print-string-
|
||||
only — no `printf`-family.
|
||||
|
||||
- `(str | rune)`-style sum-typed parameters are split into typed
|
||||
pairs (`strings.indexbyte` for the byte case, `strings.index` for
|
||||
the slice case, etc.). The Hare public name `byteindex` will come
|
||||
back once the sum-typed parameter ABI lands.
|
||||
|
||||
Don't ship a richer surface than Hare has. A documented subset is
|
||||
fine; an extension, rename, or convenience-wrapper is not — return
|
||||
shapes will change as the compiler gains tagged unions, variadics,
|
||||
and a stable `rt` allocator, and callers should not bake the current
|
||||
subset shape into themselves.
|
||||
fine; an extension, rename, or convenience-wrapper is not — callers
|
||||
should not bake the current subset shape into themselves.
|
||||
|
||||
When the compiler can express the full Hare signature, graduate the
|
||||
module — replace the `-1` / `str` shape with the tagged-union shape
|
||||
in one go and fix the callers. Don't keep both around.
|
||||
Modules with intentional divergence:
|
||||
|
||||
- `lib/os` and `lib/net` stay below the Hare abstraction — they are
|
||||
syscall wrappers, not the high-level `io::handle` / `net::socket`
|
||||
API. Use them as the foundation that `lib/io` and the buffered
|
||||
layers build on.
|
||||
- `lib/io` keeps the ww-specific `stream` struct (vtable of fn
|
||||
pointers, no closures, no methods). The Hare `io::handle` family
|
||||
needs language features we don't have yet.
|
||||
- `lib/bufio` and `lib/sort` will be redesigned to Hare's
|
||||
`scanner` / `cmpfunc` shapes; the present minimal forms are
|
||||
placeholders until then.
|
||||
- `lib/time` and `lib/math` ship only what callers need today;
|
||||
they're not aiming for parity yet.
|
||||
|
||||
When the compiler can express a Hare signature that's still in the
|
||||
ww-specific shape (e.g., once a module-level `*u8` is mutable, the
|
||||
strconv `*tos` family graduates to static-buffer returns), graduate
|
||||
the module in one go — replace the current shape with the Hare shape
|
||||
and fix the callers. Don't keep both around.
|
||||
|
||||
@@ -53,6 +53,61 @@ export fn isxdigit(c: rune) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
@@ -63,3 +118,17 @@ export fn toupper(c: rune) rune = {
|
||||
if (islower(c)) { return c - 32; };
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,17 @@ export fn indexbyte(s: []u8, c: u8) (i32 | void) = {
|
||||
return;
|
||||
};
|
||||
|
||||
// rindexbyte — last index of byte `c` in `s`. Mirrors Hare's
|
||||
// bytes::rindex for the byte case.
|
||||
export fn rindexbyte(s: []u8, c: u8) (i32 | void) = {
|
||||
let i: i32 = s.len - 1;
|
||||
for (i >= 0) {
|
||||
if (s[i] == c) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// index — first index of `sub` in `s`. Mirrors Hare's bytes::index
|
||||
// (the []u8 needle variant; the u8 needle stays as indexbyte until we
|
||||
// have union-arg dispatch). Empty `sub` matches at 0.
|
||||
@@ -41,3 +52,78 @@ export fn index(s: []u8, sub: []u8) (i32 | void) = {
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// rindex — last index of `sub` in `s`. Mirrors Hare's bytes::rindex
|
||||
// for the slice case.
|
||||
export fn rindex(s: []u8, sub: []u8) (i32 | void) = {
|
||||
if (sub.len == 0) { return s.len; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let i: i32 = s.len - sub.len;
|
||||
for (i >= 0) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// contains — true iff `sub` appears in `s`. Mirrors Hare's
|
||||
// bytes::contains for the slice case.
|
||||
export fn contains(s: []u8, sub: []u8) bool = {
|
||||
let r: (i32 | void) = index(s, sub);
|
||||
match (r) {
|
||||
case let i: i32 => return true;
|
||||
case void => return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// hasprefix — `s` starts with `pre`. Mirrors Hare's bytes::hasprefix.
|
||||
export fn hasprefix(s: []u8, pre: []u8) bool = {
|
||||
if (pre.len > s.len) { return false; };
|
||||
let i: i32 = 0;
|
||||
for (i < pre.len) {
|
||||
if (s[i] != pre[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// hassuffix — `s` ends with `suf`. Mirrors Hare's bytes::hassuffix.
|
||||
export fn hassuffix(s: []u8, suf: []u8) bool = {
|
||||
if (suf.len > s.len) { return false; };
|
||||
let off: i32 = s.len - suf.len;
|
||||
let i: i32 = 0;
|
||||
for (i < suf.len) {
|
||||
if (s[off + i] != suf[i]) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// reverse — in-place reverse of `s`. Mirrors Hare's bytes::reverse.
|
||||
export fn reverse(s: []u8) void = {
|
||||
let i: i32 = 0;
|
||||
let j: i32 = s.len - 1;
|
||||
for (i < j) {
|
||||
let t: u8 = s[i];
|
||||
s[i] = s[j];
|
||||
s[j] = t;
|
||||
i += 1;
|
||||
j -= 1;
|
||||
};
|
||||
};
|
||||
|
||||
// zero — set every byte of `s` to 0. Mirrors Hare's bytes::zero.
|
||||
export fn zero(s: []u8) void = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
s[i] = 0u8;
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,9 +1,138 @@
|
||||
// endian — byte-order conversions. Subset of Hare's endian::; only
|
||||
// the network-order helpers we need for net::. Host order on amd64 is
|
||||
// little-endian, so hton* / ntoh* are byte swaps.
|
||||
// endian — byte-order conversions. Mirrors Hare's endian:: surface:
|
||||
// big-endian (be*), little-endian (le*), and network-order (hton/ntoh)
|
||||
// helpers. Host order on amd64 is little-endian, so hton/ntoh are
|
||||
// byte swaps and the le* family is identity.
|
||||
|
||||
// ---- network order (host ↔ big-endian, since amd64 is LE) -----------
|
||||
|
||||
export fn htonu16(in: u16) u16 = {
|
||||
return ((in << 8) | (in >> 8)) & 0xffff;
|
||||
return ((in << 8u16) | (in >> 8u16)) & 0xffffu16;
|
||||
};
|
||||
export fn ntohu16(in: u16) u16 = { return htonu16(in); };
|
||||
|
||||
export fn htonu32(in: u32) u32 = {
|
||||
let b0: u32 = (in >> 24u32) & 0xffu32;
|
||||
let b1: u32 = (in >> 16u32) & 0xffu32;
|
||||
let b2: u32 = (in >> 8u32) & 0xffu32;
|
||||
let b3: u32 = in & 0xffu32;
|
||||
return (b3 << 24u32) | (b2 << 16u32) | (b1 << 8u32) | b0;
|
||||
};
|
||||
export fn ntohu32(in: u32) u32 = { return htonu32(in); };
|
||||
|
||||
export fn htonu64(in: u64) u64 = {
|
||||
let b0: u64 = (in >> 56u64) & 0xffu64;
|
||||
let b1: u64 = (in >> 48u64) & 0xffu64;
|
||||
let b2: u64 = (in >> 40u64) & 0xffu64;
|
||||
let b3: u64 = (in >> 32u64) & 0xffu64;
|
||||
let b4: u64 = (in >> 24u64) & 0xffu64;
|
||||
let b5: u64 = (in >> 16u64) & 0xffu64;
|
||||
let b6: u64 = (in >> 8u64) & 0xffu64;
|
||||
let b7: u64 = in & 0xffu64;
|
||||
return (b7 << 56u64) | (b6 << 48u64) | (b5 << 40u64) | (b4 << 32u64)
|
||||
| (b3 << 24u64) | (b2 << 16u64) | (b1 << 8u64) | b0;
|
||||
};
|
||||
export fn ntohu64(in: u64) u64 = { return htonu64(in); };
|
||||
|
||||
// ---- big-endian byte get/put on a slice ------------------------------
|
||||
|
||||
export fn begetu16(buf: []u8) u16 = {
|
||||
let b0: u16 = buf[0]: u16;
|
||||
let b1: u16 = buf[1]: u16;
|
||||
return (b0 << 8u16) | b1;
|
||||
};
|
||||
|
||||
export fn ntohu16(in: u16) u16 = htonu16(in);
|
||||
export fn beputu16(buf: []u8, in: u16) void = {
|
||||
buf[0] = ((in >> 8u16) & 0xffu16): u8;
|
||||
buf[1] = (in & 0xffu16): u8;
|
||||
};
|
||||
|
||||
export fn begetu32(buf: []u8) u32 = {
|
||||
let b0: u32 = buf[0]: u32;
|
||||
let b1: u32 = buf[1]: u32;
|
||||
let b2: u32 = buf[2]: u32;
|
||||
let b3: u32 = buf[3]: u32;
|
||||
return (b0 << 24u32) | (b1 << 16u32) | (b2 << 8u32) | b3;
|
||||
};
|
||||
|
||||
export fn beputu32(buf: []u8, in: u32) void = {
|
||||
buf[0] = ((in >> 24u32) & 0xffu32): u8;
|
||||
buf[1] = ((in >> 16u32) & 0xffu32): u8;
|
||||
buf[2] = ((in >> 8u32) & 0xffu32): u8;
|
||||
buf[3] = (in & 0xffu32): u8;
|
||||
};
|
||||
|
||||
export fn begetu64(buf: []u8) u64 = {
|
||||
let b0: u64 = buf[0]: u64;
|
||||
let b1: u64 = buf[1]: u64;
|
||||
let b2: u64 = buf[2]: u64;
|
||||
let b3: u64 = buf[3]: u64;
|
||||
let b4: u64 = buf[4]: u64;
|
||||
let b5: u64 = buf[5]: u64;
|
||||
let b6: u64 = buf[6]: u64;
|
||||
let b7: u64 = buf[7]: u64;
|
||||
return (b0 << 56u64) | (b1 << 48u64) | (b2 << 40u64) | (b3 << 32u64)
|
||||
| (b4 << 24u64) | (b5 << 16u64) | (b6 << 8u64) | b7;
|
||||
};
|
||||
|
||||
export fn beputu64(buf: []u8, in: u64) void = {
|
||||
buf[0] = ((in >> 56u64) & 0xffu64): u8;
|
||||
buf[1] = ((in >> 48u64) & 0xffu64): u8;
|
||||
buf[2] = ((in >> 40u64) & 0xffu64): u8;
|
||||
buf[3] = ((in >> 32u64) & 0xffu64): u8;
|
||||
buf[4] = ((in >> 24u64) & 0xffu64): u8;
|
||||
buf[5] = ((in >> 16u64) & 0xffu64): u8;
|
||||
buf[6] = ((in >> 8u64) & 0xffu64): u8;
|
||||
buf[7] = (in & 0xffu64): u8;
|
||||
};
|
||||
|
||||
// ---- little-endian byte get/put on a slice ---------------------------
|
||||
|
||||
export fn legetu16(buf: []u8) u16 = {
|
||||
let b0: u16 = buf[0]: u16;
|
||||
let b1: u16 = buf[1]: u16;
|
||||
return (b1 << 8u16) | b0;
|
||||
};
|
||||
|
||||
export fn leputu16(buf: []u8, in: u16) void = {
|
||||
buf[0] = (in & 0xffu16): u8;
|
||||
buf[1] = ((in >> 8u16) & 0xffu16): u8;
|
||||
};
|
||||
|
||||
export fn legetu32(buf: []u8) u32 = {
|
||||
let b0: u32 = buf[0]: u32;
|
||||
let b1: u32 = buf[1]: u32;
|
||||
let b2: u32 = buf[2]: u32;
|
||||
let b3: u32 = buf[3]: u32;
|
||||
return (b3 << 24u32) | (b2 << 16u32) | (b1 << 8u32) | b0;
|
||||
};
|
||||
|
||||
export fn leputu32(buf: []u8, in: u32) void = {
|
||||
buf[0] = (in & 0xffu32): u8;
|
||||
buf[1] = ((in >> 8u32) & 0xffu32): u8;
|
||||
buf[2] = ((in >> 16u32) & 0xffu32): u8;
|
||||
buf[3] = ((in >> 24u32) & 0xffu32): u8;
|
||||
};
|
||||
|
||||
export fn legetu64(buf: []u8) u64 = {
|
||||
let b0: u64 = buf[0]: u64;
|
||||
let b1: u64 = buf[1]: u64;
|
||||
let b2: u64 = buf[2]: u64;
|
||||
let b3: u64 = buf[3]: u64;
|
||||
let b4: u64 = buf[4]: u64;
|
||||
let b5: u64 = buf[5]: u64;
|
||||
let b6: u64 = buf[6]: u64;
|
||||
let b7: u64 = buf[7]: u64;
|
||||
return (b7 << 56u64) | (b6 << 48u64) | (b5 << 40u64) | (b4 << 32u64)
|
||||
| (b3 << 24u64) | (b2 << 16u64) | (b1 << 8u64) | b0;
|
||||
};
|
||||
|
||||
export fn leputu64(buf: []u8, in: u64) void = {
|
||||
buf[0] = (in & 0xffu64): u8;
|
||||
buf[1] = ((in >> 8u64) & 0xffu64): u8;
|
||||
buf[2] = ((in >> 16u64) & 0xffu64): u8;
|
||||
buf[3] = ((in >> 24u64) & 0xffu64): u8;
|
||||
buf[4] = ((in >> 32u64) & 0xffu64): u8;
|
||||
buf[5] = ((in >> 40u64) & 0xffu64): u8;
|
||||
buf[6] = ((in >> 48u64) & 0xffu64): u8;
|
||||
buf[7] = ((in >> 56u64) & 0xffu64): u8;
|
||||
};
|
||||
|
||||
138
lib/path/path.ww
138
lib/path/path.ww
@@ -1,8 +1,140 @@
|
||||
// path — filesystem path manipulation. UTF-8 paths, '/' separator.
|
||||
// Mirrors Hare's path:: surface. Reverse byte search lives in strings
|
||||
// (strings::rbyteindex), not here.
|
||||
// Mirrors Hare's path:: surface. ww doesn't yet ship the stack-buffer
|
||||
// path::buffer; the functions here all take a `str` and return a
|
||||
// borrowed view (basename / dirname) or a fresh owned str (join).
|
||||
|
||||
use os;
|
||||
|
||||
def SEP: u8 = 47u8; // '/'
|
||||
|
||||
// abs — `p` is an absolute path (starts with '/').
|
||||
export fn abs(p: str) bool = {
|
||||
if (p.len == 0) { return false; };
|
||||
return p[0] == ('/': u8);
|
||||
return p[0] == SEP;
|
||||
};
|
||||
|
||||
// dirname — directory component of `p`, POSIX-style. Returns a
|
||||
// borrowed view of `p` (or the static "." / "/" strings). Mirrors
|
||||
// Hare's path::dirname.
|
||||
export fn dirname(p: str) str = {
|
||||
if (p.len == 0) { return "."; };
|
||||
// Strip trailing separators.
|
||||
let n: i32 = p.len;
|
||||
for (n > 0) {
|
||||
if (p[n - 1] != SEP) { break; };
|
||||
n -= 1;
|
||||
};
|
||||
if (n == 0) { return "/"; };
|
||||
// Last separator in the trimmed prefix.
|
||||
let i: i32 = n - 1;
|
||||
for (i >= 0) {
|
||||
if (p[i] == SEP) { break; };
|
||||
i -= 1;
|
||||
};
|
||||
if (i < 0) { return "."; };
|
||||
// Strip trailing separators on the directory part too.
|
||||
for (i > 0) {
|
||||
if (p[i - 1] != SEP) { break; };
|
||||
i -= 1;
|
||||
};
|
||||
if (i == 0) { return "/"; };
|
||||
let r: str;
|
||||
r.ptr = p.ptr;
|
||||
r.len = i;
|
||||
return r;
|
||||
};
|
||||
|
||||
// basename — final path component of `p`, POSIX-style. Returns a
|
||||
// borrowed view of `p` (or "." / "/" sentinel strings). Mirrors
|
||||
// Hare's path::basename.
|
||||
export fn basename(p: str) str = {
|
||||
if (p.len == 0) { return "."; };
|
||||
let n: i32 = p.len;
|
||||
for (n > 0) {
|
||||
if (p[n - 1] != SEP) { break; };
|
||||
n -= 1;
|
||||
};
|
||||
if (n == 0) { return "/"; };
|
||||
let i: i32 = n - 1;
|
||||
for (i >= 0) {
|
||||
if (p[i] == SEP) { break; };
|
||||
i -= 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = p.ptr + ((i + 1): u64);
|
||||
r.len = n - (i + 1);
|
||||
return r;
|
||||
};
|
||||
|
||||
// extension — the final ".ext" suffix of `basename(p)`, including
|
||||
// the dot, or "" if none. Returns a borrowed view of `p`. Mirrors
|
||||
// Hare's path::extension.
|
||||
export fn extension(p: str) str = {
|
||||
let b: str = basename(p);
|
||||
let i: i32 = b.len - 1;
|
||||
for (i > 0) {
|
||||
if (b[i] == 46u8) { // '.'
|
||||
let r: str;
|
||||
r.ptr = b.ptr + (i: u64);
|
||||
r.len = b.len - i;
|
||||
return r;
|
||||
};
|
||||
i -= 1;
|
||||
};
|
||||
let empty: str;
|
||||
empty.ptr = nil;
|
||||
empty.len = 0;
|
||||
return empty;
|
||||
};
|
||||
|
||||
// join — concatenate two path components with a single '/' separator.
|
||||
// Returns owned str (release via os.free). If `b` is absolute, the
|
||||
// result is `b`. Mirrors Hare's path::buffer init+push, narrowed to
|
||||
// two-arg join (no variadic).
|
||||
export fn join(a: str, b: str) str = {
|
||||
if (abs(b)) {
|
||||
let buf: *u8 = os.alloc(b.len: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < b.len) { buf[i] = b[i]; i += 1; };
|
||||
let r: str;
|
||||
r.ptr = buf;
|
||||
r.len = b.len;
|
||||
return r;
|
||||
};
|
||||
if (a.len == 0) {
|
||||
let buf: *u8 = os.alloc(b.len: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < b.len) { buf[i] = b[i]; i += 1; };
|
||||
let r: str;
|
||||
r.ptr = buf;
|
||||
r.len = b.len;
|
||||
return r;
|
||||
};
|
||||
if (b.len == 0) {
|
||||
let buf: *u8 = os.alloc(a.len: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) { buf[i] = a[i]; i += 1; };
|
||||
let r: str;
|
||||
r.ptr = buf;
|
||||
r.len = a.len;
|
||||
return r;
|
||||
};
|
||||
// Trim trailing '/' from a; b never starts with '/' here (checked
|
||||
// above via abs(b)).
|
||||
let an: i32 = a.len;
|
||||
for (an > 0) {
|
||||
if (a[an - 1] != SEP) { break; };
|
||||
an -= 1;
|
||||
};
|
||||
let total: i32 = an + 1 + b.len;
|
||||
let buf: *u8 = os.alloc(total: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < an) { buf[i] = a[i]; i += 1; };
|
||||
buf[an] = SEP;
|
||||
let j: i32 = 0;
|
||||
for (j < b.len) { buf[an + 1 + j] = b[j]; j += 1; };
|
||||
let r: str;
|
||||
r.ptr = buf;
|
||||
r.len = total;
|
||||
return r;
|
||||
};
|
||||
|
||||
@@ -128,3 +128,91 @@ export fn dup(s: str) str = {
|
||||
r.len = s.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// rindex — last index of `sub` in `s`. Mirrors Hare's strings::rindex
|
||||
// (slice case). Empty `sub` matches at s.len.
|
||||
export fn rindex(s: str, sub: str) (i32 | void) = {
|
||||
if (sub.len == 0) { return s.len; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let i: i32 = s.len - sub.len;
|
||||
for (i >= 0) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i -= 1;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// sub — borrowed substring `s[start..end]`. Mirrors Hare's
|
||||
// strings::sub. Caller must ensure 0 <= start <= end <= s.len; out-of-
|
||||
// range indices are clamped silently here, where Hare aborts.
|
||||
export fn sub(s: str, start: i32, end: i32) str = {
|
||||
let lo: i32 = start;
|
||||
let hi: i32 = end;
|
||||
if (lo < 0) { lo = 0; };
|
||||
if (hi > s.len) { hi = s.len; };
|
||||
if (hi < lo) { hi = lo; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (lo: u64);
|
||||
r.len = hi - lo;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimprefix — `s` with `pre` stripped from the front, or `s`
|
||||
// unchanged if it doesn't start with `pre`. Returns a borrowed view.
|
||||
// Mirrors Hare's strings::trimprefix.
|
||||
export fn trimprefix(s: str, pre: str) str = {
|
||||
if (!hasprefix(s, pre)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (pre.len: u64);
|
||||
r.len = s.len - pre.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// trimsuffix — `s` with `suf` stripped from the end, or `s` unchanged
|
||||
// if it doesn't end with `suf`. Returns a borrowed view. Mirrors
|
||||
// Hare's strings::trimsuffix.
|
||||
export fn trimsuffix(s: str, suf: str) str = {
|
||||
if (!hassuffix(s, suf)) { return s; };
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = s.len - suf.len;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ltrimbyte / rtrimbyte / trimbyte — strip occurrences of a single
|
||||
// byte from the left, right, or both ends. Returns a borrowed view.
|
||||
// Hare's strings::ltrim / rtrim / trim take a rune varargs set; ww's
|
||||
// subset takes a single byte (the common ASCII case).
|
||||
export fn ltrimbyte(s: str, c: u8) str = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
if (s[i] != c) { break; };
|
||||
i += 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr + (i: u64);
|
||||
r.len = s.len - i;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn rtrimbyte(s: str, c: u8) str = {
|
||||
let n: i32 = s.len;
|
||||
for (n > 0) {
|
||||
if (s[n - 1] != c) { break; };
|
||||
n -= 1;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = s.ptr;
|
||||
r.len = n;
|
||||
return r;
|
||||
};
|
||||
|
||||
export fn trimbyte(s: str, c: u8) str = {
|
||||
return rtrimbyte(ltrimbyte(s, c), c);
|
||||
};
|
||||
|
||||
@@ -1170,6 +1170,61 @@ export fn isxdigit(c: rune) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
@@ -1181,6 +1236,20 @@ export fn toupper(c: rune) rune = {
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// MODULE: lex
|
||||
// lib/ww/lex/lex.ww — port of cmd/wcc/lex.c.
|
||||
//
|
||||
|
||||
@@ -1170,6 +1170,61 @@ export fn isxdigit(c: rune) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
@@ -1181,6 +1236,20 @@ export fn toupper(c: rune) rune = {
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// MODULE: lex
|
||||
// lib/ww/lex/lex.ww — port of cmd/wcc/lex.c.
|
||||
//
|
||||
|
||||
@@ -644,6 +644,61 @@ export fn isxdigit(c: rune) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// valid — `c` is in the 0..127 ASCII range.
|
||||
export fn valid(c: rune) bool = {
|
||||
if (c < 0) { return false; };
|
||||
if (c > 127) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// validstr — every byte in `s` is ASCII (0..127).
|
||||
export fn validstr(s: str) bool = {
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
// High-bit test rather than `> 127u8`; both cgens lower
|
||||
// the bitwise form identically. The `> u8` form picks
|
||||
// JA vs JG depending on signed/unsigned dispatch.
|
||||
if ((s[i] & 128u8) != 0u8) { return false; };
|
||||
i += 1;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// iscntrl — control chars: 0..31 and 127.
|
||||
export fn iscntrl(c: rune) bool = {
|
||||
if (c >= 0) { if (c <= 31) { return true; }; };
|
||||
if (c == 127) { return true; };
|
||||
return false;
|
||||
};
|
||||
|
||||
// isblank — space and tab.
|
||||
export fn isblank(c: rune) bool = {
|
||||
if (c == 32) { return true; }; // ' '
|
||||
if (c == 9) { return true; }; // '\t'
|
||||
return false;
|
||||
};
|
||||
|
||||
// isprint — printable: space through '~'.
|
||||
export fn isprint(c: rune) bool = {
|
||||
if (c < 32) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// isgraph — printable, non-space.
|
||||
export fn isgraph(c: rune) bool = {
|
||||
if (c < 33) { return false; };
|
||||
if (c > 126) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// ispunct — printable, non-alnum, non-space.
|
||||
export fn ispunct(c: rune) bool = {
|
||||
if (!isgraph(c)) { return false; };
|
||||
if (isalnum(c)) { return false; };
|
||||
return true;
|
||||
};
|
||||
|
||||
// tolower / toupper — fold ASCII case. Non-letters pass through.
|
||||
export fn tolower(c: rune) rune = {
|
||||
if (isupper(c)) { return c + 32; };
|
||||
@@ -655,6 +710,20 @@ export fn toupper(c: rune) rune = {
|
||||
return c;
|
||||
};
|
||||
|
||||
// strcasecmp — three-way ASCII case-insensitive compare.
|
||||
export fn strcasecmp(a: str, b: str) i32 = {
|
||||
let n: i32 = a.len;
|
||||
if (b.len < n) { n = b.len; };
|
||||
let i: i32 = 0;
|
||||
for (i < n) {
|
||||
let ca: rune = tolower(a[i]: rune);
|
||||
let cb: rune = tolower(b[i]: rune);
|
||||
if (ca != cb) { return (ca - cb): i32; };
|
||||
i += 1;
|
||||
};
|
||||
return a.len - b.len;
|
||||
};
|
||||
|
||||
// MODULE: test
|
||||
// selfhost/test/smoke.ww — end-to-end smoke for the selfhost path.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user