diff --git a/Makefile b/Makefile index 679524ed..5b54d990 100644 --- a/Makefile +++ b/Makefile @@ -113,13 +113,14 @@ $(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/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ + lib/os/os.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) cd $(BIN) && ./ww build \ -I $$PWD/../../lib/ww \ -I $$PWD/../../lib/ww/lex \ -I $$PWD/../../lib/ww/parse \ + -I $$PWD/../../lib/encoding/utf8 \ -I $$PWD/../../selfhost/cmd/wcc \ $$PWD/../../selfhost/cmd/wwdump/main.ww mv $(BIN)/main $@ @@ -134,13 +135,14 @@ $(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/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ + lib/os/os.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) cd $(BIN) && ./ww build \ -I $$PWD/../../lib/ww \ -I $$PWD/../../lib/ww/lex \ -I $$PWD/../../lib/ww/parse \ + -I $$PWD/../../lib/encoding/utf8 \ -I $$PWD/../../selfhost/cmd/wcc \ $$PWD/../../selfhost/cmd/w6c/main.ww mv $(BIN)/main $@ @@ -268,6 +270,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_shlex_run $(BIN)/test_getenv_run $(BIN)/test_dirs_run \ $(BIN)/test_stat_run $(BIN)/test_time_run \ $(BIN)/test_intdiv_signed \ + $(BIN)/test_strings_run \ $(BIN)/test_hex_run $(BIN)/test_utf8_run $(BIN)/test_bytes_run \ $(BIN)/test_memio_run $(BIN)/test_temp_run $(BIN)/test_getopt_run \ $(BIN)/test_base32_run $(BIN)/test_base64_run \ @@ -686,6 +689,10 @@ $(BIN)/test_bytes_run: test/wcc/967_bytes_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_strings_run: test/wcc/966_strings_run.c $(BIN)/ww $(BIN)/w6c \ + $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_memio_run: test/wcc/980_memio_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index d4941f5e..0cd5a4b6 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -1,12 +1,53 @@ -// strings — operations over the immutable str type ({ *u8, len }). -// Mirrors Hare's strings::; `len` and `is-empty` aren't functions -// (callers use `s.len` and `s.len == 0` directly). +// strings — operations over str ({ptr,len}). Hare port; see +// ref/hare/strings/. +// +// Documented divergences from Hare: +// +// - `concat(a, b)` is 2-arg. Hare ships `concat(strs: str...)` +// (ref/hare/strings/concat.ha:5). Blocks on task #16 (cstage +// variadic-pack drops .len of multi-field element type). Cite +// reverts on fix. +// - `trim` / `ltrim` / `rtrim` take a single rune. Hare's are +// `(exclude: rune...)` (ref/hare/strings/trim.ha:54). Same +// blocker as concat. Hare's no-rune branch (strip whitespace) +// is also dropped — depends on a rune set. +// - `contains` is non-variadic. Hare's is +// `contains(haystack, needles: (str | rune)...)` +// (ref/hare/strings/contains.ha:9). Same blocker. +// - `byteindex` / `rbyteindex` rune arms encode via +// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an +// undocumented ASCII-only restriction that silently dropped +// to the wrong byte for U+80..U+7FF and higher). +// - `dup(s: str) str` — Hare returns `(str | nomem)`. ww's +// `os.alloc` aborts on OOM (no `nomem` type), so we return plain +// `str`. Empty input returns `{nil, 0}`; Hare returns the static +// empty string — same observable result. +use bytes; +use utf8; use os; -// compare — bytewise three-way comparison: negative if ab. Matches Hare's strings::compare. ASCII-order, not -// locale-aware. Callers that just need equality use `compare(a, b) == 0`. +// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. +// `cap` equals `len`; the slice does not own a separate allocation. +export fn toutf8(s: str) []u8 = { + let r: []u8; + r.ptr = s.ptr; + r.len = s.len; + r.cap = s.len; + return r; +}; + +// fromutf8_unsafe — borrowed str view of `in`. Does not validate. +// ref/hare/strings/utf8.ha:10. +export fn fromutf8_unsafe(in: []u8) str = { + let r: str; + r.ptr = in.ptr; + r.len = in.len; + return r; +}; + +// compare — three-way bytewise codepoint-order comparison. +// ref/hare/strings/compare.ha:12. export fn compare(a: str, b: str) i32 = { let n: i32 = a.len; if (b.len < n) { n = b.len; }; @@ -18,97 +59,8 @@ export fn compare(a: str, b: str) i32 = { return a.len - b.len; }; -export fn hasprefix(s: str, p: str) bool = { - if (p.len > s.len) { return false; }; - let i: i32 = 0; - for (i < p.len) { - if (s[i] != p[i]) { return false; }; - i += 1; - }; - return true; -}; - -export fn hassuffix(s: str, suf: str) 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; -}; - -// byteindex — first byte position of `needle` in `s`. Mirrors Hare's -// strings::byteindex: a single-codepoint rune scans for the byte that -// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), -// a str needle scans for the substring. Returns void if absent. -export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { - match (needle) { - case let r: rune => { - let c: u8 = r: u8; - let i: i32 = 0; - for (i < s.len) { - if (s[i] == c) { return i; }; - i += 1; - }; - return; - }; - case let sub: str => { - if (sub.len == 0) { return 0; }; - if (sub.len > s.len) { return; }; - let last: i32 = s.len - sub.len; - let i: i32 = 0; - for (i <= last) { - 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; - }; - }; - return; -}; - -// contains — true iff `sub` appears in `s`. Mirrors Hare's -// strings::contains shape (byte-wise on the str-needle case). -export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = byteindex(s, sub); - match (r) { - case let i: i32 => return true; - case void => return false; - }; - return false; -}; - -// concat — joins two strings into a fresh str. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::concat shape. -export fn concat(a: str, b: str) str = { - let total: i32 = a.len + b.len; - let buf: *u8 = os.alloc(total: u64): *u8; - let i: i32 = 0; - for (i < a.len) { buf[i] = a[i]; i += 1; }; - let j: i32 = 0; - for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; - let r: str; - r.ptr = buf; - r.len = total; - return r; -}; - -// dup — duplicate a string into a fresh allocation. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't -// have nomem (os.alloc aborts on OOM), so we return plain `str`. -// -// Empty input yields a `{nil, 0}` str — Hare returns the static empty -// string; same observable result. +// dup — allocate a fresh copy of `s`. Caller releases with +// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7. export fn dup(s: str) str = { let r: str; r.ptr = nil; @@ -122,19 +74,14 @@ export fn dup(s: str) str = { return r; }; -// freeall — release every str element in `s` (those that were -// individually allocated) plus the slice's backing storage. Mirrors -// Hare's strings::freeall — the natural disposer for any function -// returning a fresh `[]str` of dup'd elements (e.g. shlex.split). +// freeall — release each element + the slice header. The natural +// disposer for any `[]str` of dup'd elements (e.g. shlex.split). +// ref/hare/strings/dup.ha:38. // -// Each element is freed via os.free at its own length; the slice -// header storage is freed at `cap * 16` bytes (one str = 16B). Empty -// elements (`{nil, 0}` from a zero-length dup) are skipped — calling -// os.free on a nil pointer at len 0 would tickle the rt_free guard -// that the runtime treats as a logic bug. -// -// `cap == 0` means the slice was never grown (empty `[]str` with no -// backing allocation); skip the header free in that case too. +// Empty elements (`{nil, 0}` from a zero-length dup) are skipped: +// os.free on a nil pointer at len 0 tickles the rt_free guard. The +// slice header itself is freed at `cap * 16` (one str = 16B); a +// never-grown slice (cap == 0) skips the header free. export fn freeall(s: []str) void = { let i: i32 = 0; for (i < s.len) { @@ -148,44 +95,27 @@ export fn freeall(s: []str) void = { }; }; -// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's -// strings::rbyteindex. Rune needle scans for the byte that encodes it -// (ASCII only); str needle scans for the substring. Empty str needle -// matches at s.len. -export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { - match (needle) { - case let r: rune => { - let c: u8 = r: u8; - let i: i32 = s.len - 1; - for (i >= 0) { - if (s[i] == c) { return i; }; - i -= 1; - }; - return; - }; - case let sub: str => { - 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; - }; - }; - return; +// concat — fresh allocation containing `a` then `b`. Caller releases +// with `os.free(r.ptr, r.len: u64)`. ref/hare/strings/concat.ha:5 +// (subset: Hare's `(strs: str...)` blocks on task #16). +export fn concat(a: str, b: str) str = { + let total: i32 = a.len + b.len; + let buf: *u8 = os.alloc(total: u64): *u8; + let i: i32 = 0; + for (i < a.len) { buf[i] = a[i]; i += 1; }; + let j: i32 = 0; + for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; + let r: str; + r.ptr = buf; + r.len = total; + return r; }; -// 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. +// sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is +// rune-wise; this ww form is byte-wise (no rune iterator yet, planned +// for commit 2). Clamps out-of-range silently where Hare aborts — +// retained for the existing getopt caller; will graduate when the +// rune-wise form lands. export fn sub(s: str, start: i32, end: i32) str = { let lo: i32 = start; let hi: i32 = end; @@ -198,56 +128,143 @@ export fn sub(s: str, start: i32, end: i32) str = { 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; }; +// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes) +// and return the borrowed slice trimmed to the encoded length. Hare +// inlines the same shape at ref/hare/strings/index.ha:132. +fn runebytes(scratch: []u8, r: rune) []u8 = { + let n: i32 = utf8.encoderune(scratch, r); + let s: []u8; + s.ptr = scratch.ptr; + s.len = n; + s.cap = n; + return s; +}; + +// hasprefix — true iff `in` begins with `prefix`. +// ref/hare/strings/suffix.ha:8. +export fn hasprefix(in: str, prefix: (str | rune)) bool = { + let scratch: [4]u8; + let p: []u8 = match (prefix) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hasprefix(toutf8(in), p); +}; + +// hassuffix — true iff `in` ends with `suff`. +// ref/hare/strings/suffix.ha:26. +export fn hassuffix(in: str, suff: (str | rune)) bool = { + let scratch: [4]u8; + let s: []u8 = match (suff) { + case let v: str => yield toutf8(v); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hassuffix(toutf8(in), s); +}; + +// byteindex — byte-wise offset of `needle` in `haystack`, or void if +// absent. ref/hare/strings/index.ha:127. Rune arm encodes via +// utf8.encoderune (Hare passes the encoded slice straight to +// bytes::index). +export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.index(toutf8(haystack), n); +}; + +// rbyteindex — byte-wise offset of the last `needle` in `haystack`. +// ref/hare/strings/index.ha:138. +export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.rindex(toutf8(haystack), n); +}; + +// contains — true iff `needle` occurs in `haystack`. +// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form +// `(needles: (str | rune)...)` blocks on task #16). +export fn contains(haystack: str, needle: (str | rune)) bool = { + match (byteindex(haystack, needle)) { + case let i: i32 => return true; + case void => return false; + }; + return false; +}; + +// trimprefix — `s` with `prefix` stripped from the front, or `s` +// unchanged if it doesn't start with `prefix`. Borrowed view. +// ref/hare/strings/trim.ha:60. +export fn trimprefix(input: str, prefix: str) str = { + if (!hasprefix(input, prefix)) { return input; }; let r: str; - r.ptr = s.ptr + (pre.len: u64); - r.len = s.len - pre.len; + r.ptr = input.ptr + (prefix.len: u64); + r.len = input.len - prefix.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; }; +// trimsuffix — symmetric. ref/hare/strings/trim.ha:69. +export fn trimsuffix(input: str, suffix: str) str = { + if (!hassuffix(input, suffix)) { return input; }; let r: str; - r.ptr = s.ptr; - r.len = s.len - suf.len; + r.ptr = input.ptr; + r.len = input.len - suffix.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 = { +// ltrim — strip occurrences of `exclude` (encoded as UTF-8) from the +// front. Borrowed view. ref/hare/strings/trim.ha:11 (subset: single +// rune; Hare's `(trim: rune...)` blocks on task #16). The no-rune +// strip-whitespace branch is omitted for the same reason. +export fn ltrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); let i: i32 = 0; - for (i < s.len) { - if (s[i] != c) { break; }; - i += 1; + for (i + pat.len <= input.len) { + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[i + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + i += pat.len; }; let r: str; - r.ptr = s.ptr + (i: u64); - r.len = s.len - i; + r.ptr = input.ptr + (i: u64); + r.len = input.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; +// rtrim — strip occurrences of `exclude` from the end. Borrowed view. +// ref/hare/strings/trim.ha:32 (same subset note). +export fn rtrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); + let n: i32 = input.len; + for (n >= pat.len) { + let off: i32 = n - pat.len; + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[off + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + n -= pat.len; }; let r: str; - r.ptr = s.ptr; + r.ptr = input.ptr; r.len = n; return r; }; -export fn trimbyte(s: str, c: u8) str = { - return rtrimbyte(ltrimbyte(s, c), c); +// trim — strip from both ends. ref/hare/strings/trim.ha:54. +export fn trim(input: str, exclude: rune) str = { + return ltrim(rtrim(input, exclude), exclude); }; diff --git a/lib/strings/stringstest.ww b/lib/strings/stringstest.ww new file mode 100644 index 00000000..ac0f22fb --- /dev/null +++ b/lib/strings/stringstest.ww @@ -0,0 +1,290 @@ +// stringstest — exercises lib/strings. Run with +// `out/bin/ww run lib/strings/stringstest.ww -I lib/encoding/utf8`. +// Same signalled-then-fail()-with-+10 shape as bytes / utf8 / hex / +// time tests: non-zero exit pinpoints the failing scenario. +// +// Vectors mirror ref/hare/strings/{dup,concat,trim,contains,index, +// suffix,compare}.ha where ww can express them. + +use strings; +use os; + +let signalled: i32 = 0; +fn fail() void = { os.exit(signalled + 10); }; + +fn streq(a: str, b: str) bool = { + if (a.len != b.len) { return false; }; + let i: i32 = 0; + for (i < a.len) { + if (a[i] != b[i]) { return false; }; + i += 1; + }; + return true; +}; + +// ---- dup -------------------------------------------------------------- +// ref/hare/strings/dup.ha:45. + +@test fn dup_cases() void = { + let e: str = strings.dup(""); + if (!streq(e, "")) { fail(); }; + if (e.len != 0) { fail(); }; + + let h: str = strings.dup("hello"); + if (!streq(h, "hello")) { fail(); }; + defer os.free(h.ptr: *void, h.len: u64); + + // multi-byte UTF-8: dup must copy raw bytes, not aliased view. + let m: str = strings.dup("こんにちは"); + if (m.len != 15) { fail(); }; + if (!streq(m, "こんにちは")) { fail(); }; + if (m.ptr == "こんにちは".ptr) { fail(); }; // fresh alloc + defer os.free(m.ptr: *void, m.len: u64); +}; + +// ---- concat ----------------------------------------------------------- +// ref/hare/strings/concat.ha:18 (2-arg subset). + +@test fn concat_cases() void = { + let a: str = strings.concat("hello ", "world"); + if (!streq(a, "hello world")) { fail(); }; + defer os.free(a.ptr: *void, a.len: u64); + + let e: str = strings.concat("", ""); + if (!streq(e, "")) { fail(); }; + // e.len == 0 — os.free guarded, skip. + + let l: str = strings.concat("", "world"); + if (!streq(l, "world")) { fail(); }; + defer os.free(l.ptr: *void, l.len: u64); + + let r: str = strings.concat("hello", ""); + if (!streq(r, "hello")) { fail(); }; + defer os.free(r.ptr: *void, r.len: u64); + + let m: str = strings.concat("こん", "にちは"); + if (!streq(m, "こんにちは")) { fail(); }; + defer os.free(m.ptr: *void, m.len: u64); +}; + +// ---- hasprefix -------------------------------------------------------- +// ref/hare/strings/suffix.ha:18. + +@test fn hasprefix_cases() void = { + if (!strings.hasprefix("hello world", "hello")) { fail(); }; + if (!strings.hasprefix("hello world", 'h')) { fail(); }; + if ( strings.hasprefix("hello world", "world")) { fail(); }; + if ( strings.hasprefix("hello world", 'q')) { fail(); }; + if (!strings.hasprefix("hello", "hello")) { fail(); }; // equal-len + if (!strings.hasprefix("anything", "")) { fail(); }; // empty prefix + if ( strings.hasprefix("", "x")) { fail(); }; + // multibyte rune prefix — '\'é\'' literal blocked by single-byte + // lexrune (lib/ww/lex/lex.ww:659); pass codepoint directly. + if (!strings.hasprefix("éclat", 0xE9u32: rune)) { fail(); }; + if (!strings.hasprefix("🦀rust", 0x1F980u32: rune)) { fail(); }; +}; + +// ---- hassuffix -------------------------------------------------------- +// ref/hare/strings/suffix.ha:36. + +@test fn hassuffix_cases() void = { + if (!strings.hassuffix("hello world", "world")) { fail(); }; + if (!strings.hassuffix("hello world", 'd')) { fail(); }; + if ( strings.hassuffix("hello world", "hello")) { fail(); }; + if ( strings.hassuffix("hello world", 'h')) { fail(); }; + if (!strings.hassuffix("café", 0xE9u32: rune)) { fail(); }; // multibyte +}; + +// ---- contains --------------------------------------------------------- +// ref/hare/strings/contains.ha:27. + +@test fn contains_cases() void = { + if (!strings.contains("hello world", "hello")) { fail(); }; + if (!strings.contains("hello world", 'h')) { fail(); }; + if ( strings.contains("hello world", 'x')) { fail(); }; + if (!strings.contains("hello world", "world")) { fail(); }; + if (!strings.contains("hello world", "")) { fail(); }; // empty hits at 0 + if ( strings.contains("hello world", "foobar")) { fail(); }; + if (!strings.contains("こんにちは", 0x306Bu32: rune)) { fail(); }; // 'に' + if (!strings.contains("こんにちは", "ちは")) { fail(); }; +}; + +// ---- byteindex -------------------------------------------------------- +// ref/hare/strings/index.ha:147 (byteindex tests, both arms). + +@test fn byteindex_str_cases() void = { + match (strings.byteindex("hello", "hello")) { + case let i: i32 => { if (i != 0) { fail(); }; }; + case void => { fail(); }; + }; + match (strings.byteindex("hello world!", "world")) { + case let i: i32 => { if (i != 6) { fail(); }; }; + case void => { fail(); }; + }; + match (strings.byteindex("hello world!", "orld!")) { + case let i: i32 => { if (i != 7) { fail(); }; }; + case void => { fail(); }; + }; + match (strings.byteindex("hello world!", "word")) { + case let i: i32 => { fail(); }; + case void => void; + }; + // empty needle hits at 0 (ref/hare/bytes/index.ha:63). + match (strings.byteindex("hello", "")) { + case let i: i32 => { if (i != 0) { fail(); }; }; + case void => { fail(); }; + }; + // empty haystack, non-empty needle — absent. + match (strings.byteindex("", "x")) { + case let i: i32 => { fail(); }; + case void => void; + }; + // multibyte substring in multibyte haystack. + match (strings.byteindex("こんにちは", "ちは")) { + case let i: i32 => { if (i != 9) { fail(); }; }; + case void => { fail(); }; + }; +}; + +@test fn byteindex_rune_cases() void = { + // ASCII rune (1-byte encoding). + match (strings.byteindex("hello world", 'w')) { + case let i: i32 => { if (i != 6) { fail(); }; }; + case void => { fail(); }; + }; + // 2-byte rune U+00E9 'é' inside "café". + match (strings.byteindex("café", 0xE9u32: rune)) { + case let i: i32 => { if (i != 3) { fail(); }; }; + case void => { fail(); }; + }; + // 3-byte rune U+3061 'ち' inside "こんにちは". + match (strings.byteindex("こんにちは", 0x3061u32: rune)) { + case let i: i32 => { if (i != 9) { fail(); }; }; + case void => { fail(); }; + }; + // 4-byte rune U+1F980 '🦀' inside "ab🦀cd". + match (strings.byteindex("ab🦀cd", 0x1F980u32: rune)) { + case let i: i32 => { if (i != 2) { fail(); }; }; + case void => { fail(); }; + }; + // absent. + match (strings.byteindex("こんにちは", 'q')) { + case let i: i32 => { fail(); }; + case void => void; + }; +}; + +// ---- rbyteindex ------------------------------------------------------- + +@test fn rbyteindex_cases() void = { + // Two 'た' in "またあったね" — ref/hare/strings/index.ha:160-161. + match (strings.byteindex("またあったね", "た")) { + case let i: i32 => { if (i != 3) { fail(); }; }; + case void => { fail(); }; + }; + match (strings.rbyteindex("またあったね", "た")) { + case let i: i32 => { if (i != 12) { fail(); }; }; + case void => { fail(); }; + }; + // Rune arm, multi-byte 'に' U+306B. + match (strings.rbyteindex("こんにちは", 0x306Bu32: rune)) { + case let i: i32 => { if (i != 6) { fail(); }; }; + case void => { fail(); }; + }; + // Absent. + match (strings.rbyteindex("abc", 'z')) { + case let i: i32 => { fail(); }; + case void => void; + }; +}; + +// ---- trimprefix / trimsuffix ------------------------------------------ +// ref/hare/strings/trim.ha:99-107. + +@test fn trimprefix_cases() void = { + if (!streq(strings.trimprefix("", ""), "")) { fail(); }; + if (!streq(strings.trimprefix("", "blablabla"), "")) { fail(); }; + if (!streq(strings.trimprefix("hello, world", "hello"), ", world")) { fail(); }; + if (!streq(strings.trimprefix("blablabla", "bla"), "blabla")) { fail(); }; + // equal-length match strips to empty. + if (!streq(strings.trimprefix("hello", "hello"), "")) { fail(); }; +}; + +@test fn trimsuffix_cases() void = { + if (!streq(strings.trimsuffix("", ""), "")) { fail(); }; + if (!streq(strings.trimsuffix("", "blablabla"), "")) { fail(); }; + if (!streq(strings.trimsuffix("hello, world", "world"), "hello, ")) { fail(); }; + if (!streq(strings.trimsuffix("blablabla", "bla"), "blabla")) { fail(); }; + if (!streq(strings.trimsuffix("hello", "hello"), "")) { fail(); }; +}; + +// ---- ltrim / rtrim / trim (single-rune subset) ------------------------ +// ref/hare/strings/trim.ha:75-97. Vectors restricted to single-rune +// patterns (Hare's `rune...` blocks on task #16). + +@test fn ltrim_cases() void = { + if (!streq(strings.ltrim("", 'x'), "")) { fail(); }; + if (!streq(strings.ltrim("aaabc", 'a'), "bc")) { fail(); }; + if (!streq(strings.ltrim("xyz", 'a'), "xyz")) { fail(); }; // no match + if (!streq(strings.ltrim("aaaa", 'a'), "")) { fail(); }; // all stripped + // 4-byte rune pattern — '𝚊' = U+1D68A. + if (!streq(strings.ltrim("𝚊𝚊hi", 0x1D68Au32: rune), "hi")) { fail(); }; +}; + +@test fn rtrim_cases() void = { + if (!streq(strings.rtrim("", 'x'), "")) { fail(); }; + if (!streq(strings.rtrim("bcaaa", 'a'), "bc")) { fail(); }; + if (!streq(strings.rtrim("xyz", 'a'), "xyz")) { fail(); }; + if (!streq(strings.rtrim("aaaa", 'a'), "")) { fail(); }; + if (!streq(strings.rtrim("hi𝚊𝚊", 0x1D68Au32: rune), "hi")) { fail(); }; +}; + +@test fn trim_cases() void = { + if (!streq(strings.trim("", 'x'), "")) { fail(); }; + if (!streq(strings.trim("aaabcaaa", 'a'), "bc")) { fail(); }; + if (!streq(strings.trim("xyz", 'a'), "xyz")) { fail(); }; + if (!streq(strings.trim("aaaa", 'a'), "")) { fail(); }; +}; + +// ---- compare ---------------------------------------------------------- +// ref/hare/strings/compare.ha:16. + +@test fn compare_cases() void = { + if (strings.compare("ABC", "ABC") != 0) { fail(); }; + if (strings.compare("ABC", "AB") <= 0) { fail(); }; + if (strings.compare("AB", "ABC") >= 0) { fail(); }; + if (strings.compare("BCD", "ABC") <= 0) { fail(); }; + if (strings.compare("ABC", "abc") >= 0) { fail(); }; +}; + +// ---- toutf8 / fromutf8_unsafe roundtrip ------------------------------- +// ref/hare/strings/utf8.ha:31. + +@test fn utf8_roundtrip_cases() void = { + let s: str = "hello"; + let b: []u8 = strings.toutf8(s); + if (b.len != 5) { fail(); }; + if (b[0] != 104u8) { fail(); }; // 'h' + let r: str = strings.fromutf8_unsafe(b); + if (!streq(r, "hello")) { fail(); }; + if (r.ptr != s.ptr) { fail(); }; // borrowed, not copied +}; + +export fn main() i32 = { + signalled = 1; dup_cases(); + signalled = 2; concat_cases(); + signalled = 3; hasprefix_cases(); + signalled = 4; hassuffix_cases(); + signalled = 5; contains_cases(); + signalled = 6; byteindex_str_cases(); + signalled = 7; byteindex_rune_cases(); + signalled = 8; rbyteindex_cases(); + signalled = 9; trimprefix_cases(); + signalled = 10; trimsuffix_cases(); + signalled = 11; ltrim_cases(); + signalled = 12; rtrim_cases(); + signalled = 13; trim_cases(); + signalled = 14; compare_cases(); + signalled = 15; utf8_roundtrip_cases(); + return 0; +}; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 3bacdca4..20e486a4 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -855,56 +855,38 @@ export fn freearena(a: *arena) void = { }; }; -// MODULE: strings -// strings — operations over the immutable str type ({ *u8, len }). -// Mirrors Hare's strings::; `len` and `is-empty` aren't functions -// (callers use `s.len` and `s.len == 0` directly). +// MODULE: bytes +// bytes — slice operations over []u8. Mirrors Hare's bytes module +// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix +// helpers used by lib/encoding, lib/bufio, lib/memio. +// +// Documented divergences from Hare: +// - index_slice / rindex_slice use naive O(n·m); Hare specialises +// 2/3/4-byte needles and falls back to two_way (Crochemore-Perrin) +// for longer (ref/hare/bytes/index.ha:61, ref/hare/bytes/two_way.ha). +// Correctness equivalent. +// - contains takes a single needle; Hare's contains is variadic +// `(u8 | []u8)...` (ref/hare/bytes/contains.ha:5). No caller needs +// the variadic shape yet; graduate when one does. -use os; - -// compare — bytewise three-way comparison: negative if ab. Matches Hare's strings::compare. ASCII-order, not -// locale-aware. Callers that just need equality use `compare(a, b) == 0`. -export fn compare(a: str, b: str) i32 = { - let n: i32 = a.len; - if (b.len < n) { n = b.len; }; +// equal — true iff `a` and `b` have the same length and contents. +// ref/hare/bytes/equal.ha:9. +export fn equal(a: []u8, b: []u8) bool = { + if (a.len != b.len) { return false; }; let i: i32 = 0; - for (i < n) { - if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); }; - i += 1; - }; - return a.len - b.len; -}; - -export fn hasprefix(s: str, p: str) bool = { - if (p.len > s.len) { return false; }; - let i: i32 = 0; - for (i < p.len) { - if (s[i] != p[i]) { return false; }; + for (i < a.len) { + if (a[i] != b[i]) { return false; }; i += 1; }; return true; }; -export fn hassuffix(s: str, suf: str) 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; -}; - -// byteindex — first byte position of `needle` in `s`. Mirrors Hare's -// strings::byteindex: a single-codepoint rune scans for the byte that -// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), -// a str needle scans for the substring. Returns void if absent. -export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { +// index — first offset of `needle` in `s`. u8 needle scans for the +// byte; []u8 needle scans for the substring. void if absent. +// ref/hare/bytes/index.ha:6. +export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { - case let r: rune => { - let c: u8 = r: u8; + case let c: u8 => { let i: i32 = 0; for (i < s.len) { if (s[i] == c) { return i; }; @@ -912,7 +894,7 @@ export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { }; return; }; - case let sub: str => { + case let sub: []u8 => { if (sub.len == 0) { return 0; }; if (sub.len > s.len) { return; }; let last: i32 = s.len - sub.len; @@ -933,87 +915,12 @@ export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { return; }; -// contains — true iff `sub` appears in `s`. Mirrors Hare's -// strings::contains shape (byte-wise on the str-needle case). -export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = byteindex(s, sub); - match (r) { - case let i: i32 => return true; - case void => return false; - }; - return false; -}; - -// concat — joins two strings into a fresh str. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::concat shape. -export fn concat(a: str, b: str) str = { - let total: i32 = a.len + b.len; - let buf: *u8 = os.alloc(total: u64): *u8; - let i: i32 = 0; - for (i < a.len) { buf[i] = a[i]; i += 1; }; - let j: i32 = 0; - for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; - let r: str; - r.ptr = buf; - r.len = total; - return r; -}; - -// dup — duplicate a string into a fresh allocation. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't -// have nomem (os.alloc aborts on OOM), so we return plain `str`. -// -// Empty input yields a `{nil, 0}` str — Hare returns the static empty -// string; same observable result. -export fn dup(s: str) str = { - let r: str; - r.ptr = nil; - r.len = 0; - if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; - let i: i32 = 0; - for (i < s.len) { buf[i] = s[i]; i += 1; }; - r.ptr = buf; - r.len = s.len; - return r; -}; - -// freeall — release every str element in `s` (those that were -// individually allocated) plus the slice's backing storage. Mirrors -// Hare's strings::freeall — the natural disposer for any function -// returning a fresh `[]str` of dup'd elements (e.g. shlex.split). -// -// Each element is freed via os.free at its own length; the slice -// header storage is freed at `cap * 16` bytes (one str = 16B). Empty -// elements (`{nil, 0}` from a zero-length dup) are skipped — calling -// os.free on a nil pointer at len 0 would tickle the rt_free guard -// that the runtime treats as a logic bug. -// -// `cap == 0` means the slice was never grown (empty `[]str` with no -// backing allocation); skip the header free in that case too. -export fn freeall(s: []str) void = { - let i: i32 = 0; - for (i < s.len) { - if (s[i].len > 0) { - os.free(s[i].ptr: *void, s[i].len: u64); - }; - i += 1; - }; - if (s.cap > 0) { - os.free(s.ptr: *void, (s.cap: u64) * 16u64); - }; -}; - -// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's -// strings::rbyteindex. Rune needle scans for the byte that encodes it -// (ASCII only); str needle scans for the substring. Empty str needle -// matches at s.len. -export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { +// rindex — last offset of `needle` in `s`. Empty []u8 needle returns +// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0). +// ref/hare/bytes/index.ha:86. +export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { - case let r: rune => { - let c: u8 = r: u8; + case let c: u8 => { let i: i32 = s.len - 1; for (i >= 0) { if (s[i] == c) { return i; }; @@ -1021,7 +928,7 @@ export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { }; return; }; - case let sub: str => { + case let sub: []u8 => { if (sub.len == 0) { return s.len; }; if (sub.len > s.len) { return; }; let i: i32 = s.len - sub.len; @@ -1041,9 +948,523 @@ export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { 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. +// contains — true iff `needle` (byte or sub-slice) appears in `s`. +// ref/hare/bytes/contains.ha:5 (variadic subset; see header note). +export fn contains(s: []u8, needle: (u8 | []u8)) bool = { + match (index(s, needle)) { + case let i: i32 => return true; + case void => return false; + }; + return false; +}; + +// hasprefix — true iff `s` starts with `pre`. +// ref/hare/bytes/contains.ha:21. +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 — true iff `s` ends with `suf`. +// ref/hare/bytes/contains.ha:35. +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`. ref/hare/bytes/reverse.ha:5. +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. ref/hare/bytes/zero.ha:5. +export fn zero(s: []u8) void = { + let i: i32 = 0; + for (i < s.len) { + s[i] = 0u8; + i += 1; + }; +}; + +// MODULE: utf8 +// encoding/utf8 — UTF-8 encode/decode. Hare port; see +// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. +// +// The decoder is Hoehrmann's branchless DFA, originally published +// at . Hare's +// ref/hare/encoding/utf8/decodetable.ha:4 restructures Hoehrmann's +// flat table to 2D `[8][256]i8`; we flatten back to 1D `[2048]i8` +// because ww cgen does not yet ship 2D arrays (task #20). +// +// Surface deviation from ref/hare/encoding/utf8: +// +// - `encoderune` takes a caller-supplied `out: []u8` and returns +// the byte count. Hare returns a slice into a `static let buf`; +// the caller-buffer form mirrors lib/encoding/hex.encode and +// skips the static-buffer/slice-return pair. +// +// Deferred (no in-tree caller, follow-up tasks): `prev`, `slice`, +// `position`, `remaining`, `appendrune`, `strencode`, `strdecode`. +// Hare's string-iteration surface (`strings::iterator`/`strings::next` +// — ref/hare/strings/iter.ha) lives under lib/strings, not here. + +// ref/hare/encoding/utf8/types.ha:6 — incomplete trailing sequence. +// Plain `void` (not `!void`): a truncated tail is a control-flow +// signal, not an error caller can ignore. +export type more = void; + +// ref/hare/encoding/utf8/types.ha:9 — invalid UTF-8 sequence. +export type invalid = !void; + +// `done` is not a built-in singleton in ww (Hare ships it as part of +// the type system). Plain `void` (not `!void`): end-of-input is a +// continuation signal, not an error. lib/io spells its EOF the same +// way (lib/io/io.ww:8-11). +export type done = void; + +// ref/hare/encoding/utf8/decodetable.ha:4 — Hoehrmann's UTF-8 DFA, +// flat 1D `[2048]i8`. Layout: dfa[state*256 + byte] gives the next +// state (>0), the accept transition (0 — emit rune), or invalid (-1). +// Values match ref/hare/encoding/utf8/decodetable.ha verbatim. +let dfa: [2048]i8 = [ + // state 0 — initial byte: ASCII accepts (0), continuation/illegal + // byte rejects (-1), legal multibyte start emits a state. + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 3i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 4i8, 2i8, 2i8, + 5i8, 6i8, 6i8, 6i8, 7i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 1 — expecting one continuation byte (0x80..0xBF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 2 — expecting one continuation byte (full 0x80..0xBF range). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 3 — first byte was 0xE0; continuation byte must be 0xA0..0xBF + // (rejects overlong 3-byte encodings). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 4 — first byte was 0xED; continuation byte must be 0x80..0x9F + // (rejects UTF-16 surrogate codepoints U+D800..U+DFFF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 5 — first byte was 0xF0; continuation byte must be 0x90..0xBF + // (rejects overlong 4-byte encodings). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 6 — middle continuation byte of a 4-byte sequence (0x80..0xBF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 7 — first byte was 0xF4; continuation byte must be 0x80..0x8F + // (rejects codepoints above U+10FFFF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, +]; + +// ref/hare/encoding/utf8/decode.ha:17 — payload-bit masks. Hare's +// [2][8]u8 flattened to 1D [16]u8; row 0 (offsets 0..7) is the +// continuation-byte mask (always 0x3F), row 1 (offsets 8..15) is the +// initial-byte payload mask indexed by the transition class. +let masks: [16]u8 = [ + 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, + 0x7fu8, 0x1fu8, 0x0fu8, 0x0fu8, 0x0fu8, 0x07u8, 0x07u8, 0x07u8, +]; + +// ref/hare/encoding/utf8/decode.ha:6 — incremental decoder state. +export type decoder = struct { + offs: i32, + src: []u8, +}; + +// ref/hare/encoding/utf8/decode.ha:12. +export fn decode(src: []u8) decoder = { + let d: decoder; + d.src = src; + d.offs = 0; + return d; +}; + +// ref/hare/encoding/utf8/decode.ha:27. Returns the next rune from a +// decoder, `done` at end-of-input, `more` on truncated trailing +// sequence, `invalid` on malformed input (overlong, surrogate, +// out-of-range, bad continuation). +// +// Algorithm is verbatim Hoehrmann (see file header). One structural +// rewrite: Hare encodes the "initial vs continuation byte" decision +// as the branchless `(state - 1): uint >> 31`, which assumes a 32-bit +// uint. ww's uint is 64-bit (cmd/wcc/type.c:58), so the shift answer +// would be 0x1_ffff_ffff rather than 1. We spell the same predicate +// with an explicit conditional. +export fn next(d: *decoder) (rune | done | more | invalid) = { + if (d.offs == d.src.len) { + let dn: done; return dn; + }; + let nx: i32 = 0; + let state: i32 = 0; + let r: u32 = 0u32; + for (d.offs < d.src.len) { + let b: u8 = d.src[d.offs]; + let bi: i32 = b: i32; + let row: i32 = state * 256 + bi; + let cell: i8 = dfa[row]; + nx = cell: i32; + let mi: i32 = 0; + if (state == 0) { mi = 1; }; + let m: u8 = masks[mi * 8 + (nx & 7)]; + r = (r << 6u32) | ((b & m): u32); + if (nx <= 0) { + d.offs += 1; + if (nx == 0) { return r: rune; }; + let e: invalid; return e; + }; + state = nx; + d.offs += 1; + }; + let mr: more; return mr; +}; + +// ref/hare/encoding/utf8/decode.ha:207. Strict whole-input check. +// The hot path: tight DFA loop, no rune assembly. Bails the moment +// the table returns -1 so malformed inputs don't pay for the rest +// of the buffer. +export fn validate(src: []u8) (void | invalid) = { + let state: i32 = 0; + let i: i32 = 0; + for (i < src.len) { + if (state < 0) { break; }; + let bi: i32 = src[i]: i32; + let cell: i8 = dfa[state * 256 + bi]; + state = cell: i32; + i += 1; + }; + if (state == 0) { return; }; + let e: invalid; return e; +}; + +// ref/hare/encoding/utf8/rune.ha:5. Encoded byte length of `r` as +// UTF-8. Callers in ww use this to size the buffer they hand to +// [[encoderune]]; values >0x10FFFF or negative are not legal Unicode +// codepoints and Hare aborts on them in `encoderune` itself, so we +// keep `runesz` infallible (matches Hare). +export fn runesz(r: rune) i32 = { + let ch: u32 = r: u32; + if (ch < 128u32) { return 1; }; + if (ch < 2048u32) { return 2; }; + if (ch < 65536u32) { return 3; }; + return 4; +}; + +// ref/hare/encoding/utf8/rune.ha:15. Expected byte length of the +// codepoint that starts with `c`, or `invalid` if `c` cannot start +// a legal UTF-8 sequence. Constants written in decimal because ww +// doesn't accept Hare's `0b1000_0000` binary syntax: 0x80=128, +// 0xC2=194, 0xE0=224, 0xF0=240, 0xF8=248. +export fn utf8sz(c: u8) (i32 | invalid) = { + if (c < 128u8) { return 1; }; + if (c < 194u8) { let e: invalid; return e; }; + if (c >= 248u8) { let e: invalid; return e; }; + if (c < 224u8) { return 2; }; + if (c < 240u8) { return 3; }; + return 4; +}; + +// ref/hare/encoding/utf8/encode.ha:7. Encode `r` into `out` (caller- +// supplied; must hold at least [[runesz]](r) bytes) and return the +// byte count. ABORT if `r` is a UTF-16 surrogate or above U+10FFFF — +// same precondition Hare asserts at ref/hare/encoding/utf8/encode.ha:9. +// +// Surface deviation: Hare returns `[]u8` (slice into a static buf). +// ww uses the caller-buffer form (matches lib/encoding/hex.encode); +// caller can reuse a [4]u8 stack scratch across encodes. +export fn encoderune(out: []u8, r: rune) i32 = { + let ch: u32 = r: u32; + if (ch >= 0xD800u32) { + if (ch <= 0xDFFFu32) { + abort("utf8.encoderune: surrogate codepoint"); + }; + }; + if (ch > 0x10FFFFu32) { + abort("utf8.encoderune: codepoint > U+10FFFF"); + }; + + let n: i32 = 0; + let first: u8 = 0u8; + if (ch < 0x80u32) { + first = 0u8; n = 1; + } else if (ch < 0x800u32) { + first = 0xC0u8; n = 2; + } else if (ch < 0x10000u32) { + first = 0xE0u8; n = 3; + } else { + first = 0xF0u8; n = 4; + }; + + let v: u32 = ch; + let i: i32 = n - 1; + for (i > 0) { + out[i] = ((v: u8) & 0x3Fu8) | 0x80u8; + v = v >> 6u32; + i -= 1; + }; + out[0] = (v: u8) | first; + return n; +}; + + +// MODULE: strings +// strings — operations over str ({ptr,len}). Hare port; see +// ref/hare/strings/. +// +// Documented divergences from Hare: +// +// - `concat(a, b)` is 2-arg. Hare ships `concat(strs: str...)` +// (ref/hare/strings/concat.ha:5). Blocks on task #16 (cstage +// variadic-pack drops .len of multi-field element type). Cite +// reverts on fix. +// - `trim` / `ltrim` / `rtrim` take a single rune. Hare's are +// `(exclude: rune...)` (ref/hare/strings/trim.ha:54). Same +// blocker as concat. Hare's no-rune branch (strip whitespace) +// is also dropped — depends on a rune set. +// - `contains` is non-variadic. Hare's is +// `contains(haystack, needles: (str | rune)...)` +// (ref/hare/strings/contains.ha:9). Same blocker. +// - `byteindex` / `rbyteindex` rune arms encode via +// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an +// undocumented ASCII-only restriction that silently dropped +// to the wrong byte for U+80..U+7FF and higher). +// - `dup(s: str) str` — Hare returns `(str | nomem)`. ww's +// `os.alloc` aborts on OOM (no `nomem` type), so we return plain +// `str`. Empty input returns `{nil, 0}`; Hare returns the static +// empty string — same observable result. + +use bytes; +use utf8; +use os; + +// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. +// `cap` equals `len`; the slice does not own a separate allocation. +export fn toutf8(s: str) []u8 = { + let r: []u8; + r.ptr = s.ptr; + r.len = s.len; + r.cap = s.len; + return r; +}; + +// fromutf8_unsafe — borrowed str view of `in`. Does not validate. +// ref/hare/strings/utf8.ha:10. +export fn fromutf8_unsafe(in: []u8) str = { + let r: str; + r.ptr = in.ptr; + r.len = in.len; + return r; +}; + +// compare — three-way bytewise codepoint-order comparison. +// ref/hare/strings/compare.ha:12. +export fn compare(a: str, b: str) i32 = { + let n: i32 = a.len; + if (b.len < n) { n = b.len; }; + let i: i32 = 0; + for (i < n) { + if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); }; + i += 1; + }; + return a.len - b.len; +}; + +// dup — allocate a fresh copy of `s`. Caller releases with +// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7. +export fn dup(s: str) str = { + let r: str; + r.ptr = nil; + r.len = 0; + if (s.len == 0) { return r; }; + let buf: *u8 = os.alloc(s.len: u64): *u8; + let i: i32 = 0; + for (i < s.len) { buf[i] = s[i]; i += 1; }; + r.ptr = buf; + r.len = s.len; + return r; +}; + +// freeall — release each element + the slice header. The natural +// disposer for any `[]str` of dup'd elements (e.g. shlex.split). +// ref/hare/strings/dup.ha:38. +// +// Empty elements (`{nil, 0}` from a zero-length dup) are skipped: +// os.free on a nil pointer at len 0 tickles the rt_free guard. The +// slice header itself is freed at `cap * 16` (one str = 16B); a +// never-grown slice (cap == 0) skips the header free. +export fn freeall(s: []str) void = { + let i: i32 = 0; + for (i < s.len) { + if (s[i].len > 0) { + os.free(s[i].ptr: *void, s[i].len: u64); + }; + i += 1; + }; + if (s.cap > 0) { + os.free(s.ptr: *void, (s.cap: u64) * 16u64); + }; +}; + +// concat — fresh allocation containing `a` then `b`. Caller releases +// with `os.free(r.ptr, r.len: u64)`. ref/hare/strings/concat.ha:5 +// (subset: Hare's `(strs: str...)` blocks on task #16). +export fn concat(a: str, b: str) str = { + let total: i32 = a.len + b.len; + let buf: *u8 = os.alloc(total: u64): *u8; + let i: i32 = 0; + for (i < a.len) { buf[i] = a[i]; i += 1; }; + let j: i32 = 0; + for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; + let r: str; + r.ptr = buf; + r.len = total; + return r; +}; + +// sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is +// rune-wise; this ww form is byte-wise (no rune iterator yet, planned +// for commit 2). Clamps out-of-range silently where Hare aborts — +// retained for the existing getopt caller; will graduate when the +// rune-wise form lands. export fn sub(s: str, start: i32, end: i32) str = { let lo: i32 = start; let hi: i32 = end; @@ -1056,58 +1477,145 @@ export fn sub(s: str, start: i32, end: i32) str = { 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; }; +// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes) +// and return the borrowed slice trimmed to the encoded length. Hare +// inlines the same shape at ref/hare/strings/index.ha:132. +fn runebytes(scratch: []u8, r: rune) []u8 = { + let n: i32 = utf8.encoderune(scratch, r); + let s: []u8; + s.ptr = scratch.ptr; + s.len = n; + s.cap = n; + return s; +}; + +// hasprefix — true iff `in` begins with `prefix`. +// ref/hare/strings/suffix.ha:8. +export fn hasprefix(in: str, prefix: (str | rune)) bool = { + let scratch: [4]u8; + let p: []u8 = match (prefix) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hasprefix(toutf8(in), p); +}; + +// hassuffix — true iff `in` ends with `suff`. +// ref/hare/strings/suffix.ha:26. +export fn hassuffix(in: str, suff: (str | rune)) bool = { + let scratch: [4]u8; + let s: []u8 = match (suff) { + case let v: str => yield toutf8(v); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hassuffix(toutf8(in), s); +}; + +// byteindex — byte-wise offset of `needle` in `haystack`, or void if +// absent. ref/hare/strings/index.ha:127. Rune arm encodes via +// utf8.encoderune (Hare passes the encoded slice straight to +// bytes::index). +export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.index(toutf8(haystack), n); +}; + +// rbyteindex — byte-wise offset of the last `needle` in `haystack`. +// ref/hare/strings/index.ha:138. +export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.rindex(toutf8(haystack), n); +}; + +// contains — true iff `needle` occurs in `haystack`. +// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form +// `(needles: (str | rune)...)` blocks on task #16). +export fn contains(haystack: str, needle: (str | rune)) bool = { + match (byteindex(haystack, needle)) { + case let i: i32 => return true; + case void => return false; + }; + return false; +}; + +// trimprefix — `s` with `prefix` stripped from the front, or `s` +// unchanged if it doesn't start with `prefix`. Borrowed view. +// ref/hare/strings/trim.ha:60. +export fn trimprefix(input: str, prefix: str) str = { + if (!hasprefix(input, prefix)) { return input; }; let r: str; - r.ptr = s.ptr + (pre.len: u64); - r.len = s.len - pre.len; + r.ptr = input.ptr + (prefix.len: u64); + r.len = input.len - prefix.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; }; +// trimsuffix — symmetric. ref/hare/strings/trim.ha:69. +export fn trimsuffix(input: str, suffix: str) str = { + if (!hassuffix(input, suffix)) { return input; }; let r: str; - r.ptr = s.ptr; - r.len = s.len - suf.len; + r.ptr = input.ptr; + r.len = input.len - suffix.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 = { +// ltrim — strip occurrences of `exclude` (encoded as UTF-8) from the +// front. Borrowed view. ref/hare/strings/trim.ha:11 (subset: single +// rune; Hare's `(trim: rune...)` blocks on task #16). The no-rune +// strip-whitespace branch is omitted for the same reason. +export fn ltrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); let i: i32 = 0; - for (i < s.len) { - if (s[i] != c) { break; }; - i += 1; + for (i + pat.len <= input.len) { + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[i + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + i += pat.len; }; let r: str; - r.ptr = s.ptr + (i: u64); - r.len = s.len - i; + r.ptr = input.ptr + (i: u64); + r.len = input.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; +// rtrim — strip occurrences of `exclude` from the end. Borrowed view. +// ref/hare/strings/trim.ha:32 (same subset note). +export fn rtrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); + let n: i32 = input.len; + for (n >= pat.len) { + let off: i32 = n - pat.len; + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[off + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + n -= pat.len; }; let r: str; - r.ptr = s.ptr; + r.ptr = input.ptr; r.len = n; return r; }; -export fn trimbyte(s: str, c: u8) str = { - return rtrimbyte(ltrimbyte(s, c), c); +// trim — strip from both ends. ref/hare/strings/trim.ha:54. +export fn trim(input: str, exclude: rune) str = { + return ltrim(rtrim(input, exclude), exclude); }; // MODULE: strconv diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 223b224d..7ca43677 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -855,56 +855,38 @@ export fn freearena(a: *arena) void = { }; }; -// MODULE: strings -// strings — operations over the immutable str type ({ *u8, len }). -// Mirrors Hare's strings::; `len` and `is-empty` aren't functions -// (callers use `s.len` and `s.len == 0` directly). +// MODULE: bytes +// bytes — slice operations over []u8. Mirrors Hare's bytes module +// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix +// helpers used by lib/encoding, lib/bufio, lib/memio. +// +// Documented divergences from Hare: +// - index_slice / rindex_slice use naive O(n·m); Hare specialises +// 2/3/4-byte needles and falls back to two_way (Crochemore-Perrin) +// for longer (ref/hare/bytes/index.ha:61, ref/hare/bytes/two_way.ha). +// Correctness equivalent. +// - contains takes a single needle; Hare's contains is variadic +// `(u8 | []u8)...` (ref/hare/bytes/contains.ha:5). No caller needs +// the variadic shape yet; graduate when one does. -use os; - -// compare — bytewise three-way comparison: negative if ab. Matches Hare's strings::compare. ASCII-order, not -// locale-aware. Callers that just need equality use `compare(a, b) == 0`. -export fn compare(a: str, b: str) i32 = { - let n: i32 = a.len; - if (b.len < n) { n = b.len; }; +// equal — true iff `a` and `b` have the same length and contents. +// ref/hare/bytes/equal.ha:9. +export fn equal(a: []u8, b: []u8) bool = { + if (a.len != b.len) { return false; }; let i: i32 = 0; - for (i < n) { - if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); }; - i += 1; - }; - return a.len - b.len; -}; - -export fn hasprefix(s: str, p: str) bool = { - if (p.len > s.len) { return false; }; - let i: i32 = 0; - for (i < p.len) { - if (s[i] != p[i]) { return false; }; + for (i < a.len) { + if (a[i] != b[i]) { return false; }; i += 1; }; return true; }; -export fn hassuffix(s: str, suf: str) 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; -}; - -// byteindex — first byte position of `needle` in `s`. Mirrors Hare's -// strings::byteindex: a single-codepoint rune scans for the byte that -// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), -// a str needle scans for the substring. Returns void if absent. -export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { +// index — first offset of `needle` in `s`. u8 needle scans for the +// byte; []u8 needle scans for the substring. void if absent. +// ref/hare/bytes/index.ha:6. +export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { - case let r: rune => { - let c: u8 = r: u8; + case let c: u8 => { let i: i32 = 0; for (i < s.len) { if (s[i] == c) { return i; }; @@ -912,7 +894,7 @@ export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { }; return; }; - case let sub: str => { + case let sub: []u8 => { if (sub.len == 0) { return 0; }; if (sub.len > s.len) { return; }; let last: i32 = s.len - sub.len; @@ -933,87 +915,12 @@ export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { return; }; -// contains — true iff `sub` appears in `s`. Mirrors Hare's -// strings::contains shape (byte-wise on the str-needle case). -export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = byteindex(s, sub); - match (r) { - case let i: i32 => return true; - case void => return false; - }; - return false; -}; - -// concat — joins two strings into a fresh str. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::concat shape. -export fn concat(a: str, b: str) str = { - let total: i32 = a.len + b.len; - let buf: *u8 = os.alloc(total: u64): *u8; - let i: i32 = 0; - for (i < a.len) { buf[i] = a[i]; i += 1; }; - let j: i32 = 0; - for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; - let r: str; - r.ptr = buf; - r.len = total; - return r; -}; - -// dup — duplicate a string into a fresh allocation. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't -// have nomem (os.alloc aborts on OOM), so we return plain `str`. -// -// Empty input yields a `{nil, 0}` str — Hare returns the static empty -// string; same observable result. -export fn dup(s: str) str = { - let r: str; - r.ptr = nil; - r.len = 0; - if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; - let i: i32 = 0; - for (i < s.len) { buf[i] = s[i]; i += 1; }; - r.ptr = buf; - r.len = s.len; - return r; -}; - -// freeall — release every str element in `s` (those that were -// individually allocated) plus the slice's backing storage. Mirrors -// Hare's strings::freeall — the natural disposer for any function -// returning a fresh `[]str` of dup'd elements (e.g. shlex.split). -// -// Each element is freed via os.free at its own length; the slice -// header storage is freed at `cap * 16` bytes (one str = 16B). Empty -// elements (`{nil, 0}` from a zero-length dup) are skipped — calling -// os.free on a nil pointer at len 0 would tickle the rt_free guard -// that the runtime treats as a logic bug. -// -// `cap == 0` means the slice was never grown (empty `[]str` with no -// backing allocation); skip the header free in that case too. -export fn freeall(s: []str) void = { - let i: i32 = 0; - for (i < s.len) { - if (s[i].len > 0) { - os.free(s[i].ptr: *void, s[i].len: u64); - }; - i += 1; - }; - if (s.cap > 0) { - os.free(s.ptr: *void, (s.cap: u64) * 16u64); - }; -}; - -// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's -// strings::rbyteindex. Rune needle scans for the byte that encodes it -// (ASCII only); str needle scans for the substring. Empty str needle -// matches at s.len. -export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { +// rindex — last offset of `needle` in `s`. Empty []u8 needle returns +// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0). +// ref/hare/bytes/index.ha:86. +export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { - case let r: rune => { - let c: u8 = r: u8; + case let c: u8 => { let i: i32 = s.len - 1; for (i >= 0) { if (s[i] == c) { return i; }; @@ -1021,7 +928,7 @@ export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { }; return; }; - case let sub: str => { + case let sub: []u8 => { if (sub.len == 0) { return s.len; }; if (sub.len > s.len) { return; }; let i: i32 = s.len - sub.len; @@ -1041,9 +948,523 @@ export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { 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. +// contains — true iff `needle` (byte or sub-slice) appears in `s`. +// ref/hare/bytes/contains.ha:5 (variadic subset; see header note). +export fn contains(s: []u8, needle: (u8 | []u8)) bool = { + match (index(s, needle)) { + case let i: i32 => return true; + case void => return false; + }; + return false; +}; + +// hasprefix — true iff `s` starts with `pre`. +// ref/hare/bytes/contains.ha:21. +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 — true iff `s` ends with `suf`. +// ref/hare/bytes/contains.ha:35. +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`. ref/hare/bytes/reverse.ha:5. +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. ref/hare/bytes/zero.ha:5. +export fn zero(s: []u8) void = { + let i: i32 = 0; + for (i < s.len) { + s[i] = 0u8; + i += 1; + }; +}; + +// MODULE: utf8 +// encoding/utf8 — UTF-8 encode/decode. Hare port; see +// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. +// +// The decoder is Hoehrmann's branchless DFA, originally published +// at . Hare's +// ref/hare/encoding/utf8/decodetable.ha:4 restructures Hoehrmann's +// flat table to 2D `[8][256]i8`; we flatten back to 1D `[2048]i8` +// because ww cgen does not yet ship 2D arrays (task #20). +// +// Surface deviation from ref/hare/encoding/utf8: +// +// - `encoderune` takes a caller-supplied `out: []u8` and returns +// the byte count. Hare returns a slice into a `static let buf`; +// the caller-buffer form mirrors lib/encoding/hex.encode and +// skips the static-buffer/slice-return pair. +// +// Deferred (no in-tree caller, follow-up tasks): `prev`, `slice`, +// `position`, `remaining`, `appendrune`, `strencode`, `strdecode`. +// Hare's string-iteration surface (`strings::iterator`/`strings::next` +// — ref/hare/strings/iter.ha) lives under lib/strings, not here. + +// ref/hare/encoding/utf8/types.ha:6 — incomplete trailing sequence. +// Plain `void` (not `!void`): a truncated tail is a control-flow +// signal, not an error caller can ignore. +export type more = void; + +// ref/hare/encoding/utf8/types.ha:9 — invalid UTF-8 sequence. +export type invalid = !void; + +// `done` is not a built-in singleton in ww (Hare ships it as part of +// the type system). Plain `void` (not `!void`): end-of-input is a +// continuation signal, not an error. lib/io spells its EOF the same +// way (lib/io/io.ww:8-11). +export type done = void; + +// ref/hare/encoding/utf8/decodetable.ha:4 — Hoehrmann's UTF-8 DFA, +// flat 1D `[2048]i8`. Layout: dfa[state*256 + byte] gives the next +// state (>0), the accept transition (0 — emit rune), or invalid (-1). +// Values match ref/hare/encoding/utf8/decodetable.ha verbatim. +let dfa: [2048]i8 = [ + // state 0 — initial byte: ASCII accepts (0), continuation/illegal + // byte rejects (-1), legal multibyte start emits a state. + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 3i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 4i8, 2i8, 2i8, + 5i8, 6i8, 6i8, 6i8, 7i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 1 — expecting one continuation byte (0x80..0xBF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 2 — expecting one continuation byte (full 0x80..0xBF range). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 3 — first byte was 0xE0; continuation byte must be 0xA0..0xBF + // (rejects overlong 3-byte encodings). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 4 — first byte was 0xED; continuation byte must be 0x80..0x9F + // (rejects UTF-16 surrogate codepoints U+D800..U+DFFF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 5 — first byte was 0xF0; continuation byte must be 0x90..0xBF + // (rejects overlong 4-byte encodings). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 6 — middle continuation byte of a 4-byte sequence (0x80..0xBF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 7 — first byte was 0xF4; continuation byte must be 0x80..0x8F + // (rejects codepoints above U+10FFFF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, +]; + +// ref/hare/encoding/utf8/decode.ha:17 — payload-bit masks. Hare's +// [2][8]u8 flattened to 1D [16]u8; row 0 (offsets 0..7) is the +// continuation-byte mask (always 0x3F), row 1 (offsets 8..15) is the +// initial-byte payload mask indexed by the transition class. +let masks: [16]u8 = [ + 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, + 0x7fu8, 0x1fu8, 0x0fu8, 0x0fu8, 0x0fu8, 0x07u8, 0x07u8, 0x07u8, +]; + +// ref/hare/encoding/utf8/decode.ha:6 — incremental decoder state. +export type decoder = struct { + offs: i32, + src: []u8, +}; + +// ref/hare/encoding/utf8/decode.ha:12. +export fn decode(src: []u8) decoder = { + let d: decoder; + d.src = src; + d.offs = 0; + return d; +}; + +// ref/hare/encoding/utf8/decode.ha:27. Returns the next rune from a +// decoder, `done` at end-of-input, `more` on truncated trailing +// sequence, `invalid` on malformed input (overlong, surrogate, +// out-of-range, bad continuation). +// +// Algorithm is verbatim Hoehrmann (see file header). One structural +// rewrite: Hare encodes the "initial vs continuation byte" decision +// as the branchless `(state - 1): uint >> 31`, which assumes a 32-bit +// uint. ww's uint is 64-bit (cmd/wcc/type.c:58), so the shift answer +// would be 0x1_ffff_ffff rather than 1. We spell the same predicate +// with an explicit conditional. +export fn next(d: *decoder) (rune | done | more | invalid) = { + if (d.offs == d.src.len) { + let dn: done; return dn; + }; + let nx: i32 = 0; + let state: i32 = 0; + let r: u32 = 0u32; + for (d.offs < d.src.len) { + let b: u8 = d.src[d.offs]; + let bi: i32 = b: i32; + let row: i32 = state * 256 + bi; + let cell: i8 = dfa[row]; + nx = cell: i32; + let mi: i32 = 0; + if (state == 0) { mi = 1; }; + let m: u8 = masks[mi * 8 + (nx & 7)]; + r = (r << 6u32) | ((b & m): u32); + if (nx <= 0) { + d.offs += 1; + if (nx == 0) { return r: rune; }; + let e: invalid; return e; + }; + state = nx; + d.offs += 1; + }; + let mr: more; return mr; +}; + +// ref/hare/encoding/utf8/decode.ha:207. Strict whole-input check. +// The hot path: tight DFA loop, no rune assembly. Bails the moment +// the table returns -1 so malformed inputs don't pay for the rest +// of the buffer. +export fn validate(src: []u8) (void | invalid) = { + let state: i32 = 0; + let i: i32 = 0; + for (i < src.len) { + if (state < 0) { break; }; + let bi: i32 = src[i]: i32; + let cell: i8 = dfa[state * 256 + bi]; + state = cell: i32; + i += 1; + }; + if (state == 0) { return; }; + let e: invalid; return e; +}; + +// ref/hare/encoding/utf8/rune.ha:5. Encoded byte length of `r` as +// UTF-8. Callers in ww use this to size the buffer they hand to +// [[encoderune]]; values >0x10FFFF or negative are not legal Unicode +// codepoints and Hare aborts on them in `encoderune` itself, so we +// keep `runesz` infallible (matches Hare). +export fn runesz(r: rune) i32 = { + let ch: u32 = r: u32; + if (ch < 128u32) { return 1; }; + if (ch < 2048u32) { return 2; }; + if (ch < 65536u32) { return 3; }; + return 4; +}; + +// ref/hare/encoding/utf8/rune.ha:15. Expected byte length of the +// codepoint that starts with `c`, or `invalid` if `c` cannot start +// a legal UTF-8 sequence. Constants written in decimal because ww +// doesn't accept Hare's `0b1000_0000` binary syntax: 0x80=128, +// 0xC2=194, 0xE0=224, 0xF0=240, 0xF8=248. +export fn utf8sz(c: u8) (i32 | invalid) = { + if (c < 128u8) { return 1; }; + if (c < 194u8) { let e: invalid; return e; }; + if (c >= 248u8) { let e: invalid; return e; }; + if (c < 224u8) { return 2; }; + if (c < 240u8) { return 3; }; + return 4; +}; + +// ref/hare/encoding/utf8/encode.ha:7. Encode `r` into `out` (caller- +// supplied; must hold at least [[runesz]](r) bytes) and return the +// byte count. ABORT if `r` is a UTF-16 surrogate or above U+10FFFF — +// same precondition Hare asserts at ref/hare/encoding/utf8/encode.ha:9. +// +// Surface deviation: Hare returns `[]u8` (slice into a static buf). +// ww uses the caller-buffer form (matches lib/encoding/hex.encode); +// caller can reuse a [4]u8 stack scratch across encodes. +export fn encoderune(out: []u8, r: rune) i32 = { + let ch: u32 = r: u32; + if (ch >= 0xD800u32) { + if (ch <= 0xDFFFu32) { + abort("utf8.encoderune: surrogate codepoint"); + }; + }; + if (ch > 0x10FFFFu32) { + abort("utf8.encoderune: codepoint > U+10FFFF"); + }; + + let n: i32 = 0; + let first: u8 = 0u8; + if (ch < 0x80u32) { + first = 0u8; n = 1; + } else if (ch < 0x800u32) { + first = 0xC0u8; n = 2; + } else if (ch < 0x10000u32) { + first = 0xE0u8; n = 3; + } else { + first = 0xF0u8; n = 4; + }; + + let v: u32 = ch; + let i: i32 = n - 1; + for (i > 0) { + out[i] = ((v: u8) & 0x3Fu8) | 0x80u8; + v = v >> 6u32; + i -= 1; + }; + out[0] = (v: u8) | first; + return n; +}; + + +// MODULE: strings +// strings — operations over str ({ptr,len}). Hare port; see +// ref/hare/strings/. +// +// Documented divergences from Hare: +// +// - `concat(a, b)` is 2-arg. Hare ships `concat(strs: str...)` +// (ref/hare/strings/concat.ha:5). Blocks on task #16 (cstage +// variadic-pack drops .len of multi-field element type). Cite +// reverts on fix. +// - `trim` / `ltrim` / `rtrim` take a single rune. Hare's are +// `(exclude: rune...)` (ref/hare/strings/trim.ha:54). Same +// blocker as concat. Hare's no-rune branch (strip whitespace) +// is also dropped — depends on a rune set. +// - `contains` is non-variadic. Hare's is +// `contains(haystack, needles: (str | rune)...)` +// (ref/hare/strings/contains.ha:9). Same blocker. +// - `byteindex` / `rbyteindex` rune arms encode via +// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an +// undocumented ASCII-only restriction that silently dropped +// to the wrong byte for U+80..U+7FF and higher). +// - `dup(s: str) str` — Hare returns `(str | nomem)`. ww's +// `os.alloc` aborts on OOM (no `nomem` type), so we return plain +// `str`. Empty input returns `{nil, 0}`; Hare returns the static +// empty string — same observable result. + +use bytes; +use utf8; +use os; + +// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. +// `cap` equals `len`; the slice does not own a separate allocation. +export fn toutf8(s: str) []u8 = { + let r: []u8; + r.ptr = s.ptr; + r.len = s.len; + r.cap = s.len; + return r; +}; + +// fromutf8_unsafe — borrowed str view of `in`. Does not validate. +// ref/hare/strings/utf8.ha:10. +export fn fromutf8_unsafe(in: []u8) str = { + let r: str; + r.ptr = in.ptr; + r.len = in.len; + return r; +}; + +// compare — three-way bytewise codepoint-order comparison. +// ref/hare/strings/compare.ha:12. +export fn compare(a: str, b: str) i32 = { + let n: i32 = a.len; + if (b.len < n) { n = b.len; }; + let i: i32 = 0; + for (i < n) { + if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); }; + i += 1; + }; + return a.len - b.len; +}; + +// dup — allocate a fresh copy of `s`. Caller releases with +// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7. +export fn dup(s: str) str = { + let r: str; + r.ptr = nil; + r.len = 0; + if (s.len == 0) { return r; }; + let buf: *u8 = os.alloc(s.len: u64): *u8; + let i: i32 = 0; + for (i < s.len) { buf[i] = s[i]; i += 1; }; + r.ptr = buf; + r.len = s.len; + return r; +}; + +// freeall — release each element + the slice header. The natural +// disposer for any `[]str` of dup'd elements (e.g. shlex.split). +// ref/hare/strings/dup.ha:38. +// +// Empty elements (`{nil, 0}` from a zero-length dup) are skipped: +// os.free on a nil pointer at len 0 tickles the rt_free guard. The +// slice header itself is freed at `cap * 16` (one str = 16B); a +// never-grown slice (cap == 0) skips the header free. +export fn freeall(s: []str) void = { + let i: i32 = 0; + for (i < s.len) { + if (s[i].len > 0) { + os.free(s[i].ptr: *void, s[i].len: u64); + }; + i += 1; + }; + if (s.cap > 0) { + os.free(s.ptr: *void, (s.cap: u64) * 16u64); + }; +}; + +// concat — fresh allocation containing `a` then `b`. Caller releases +// with `os.free(r.ptr, r.len: u64)`. ref/hare/strings/concat.ha:5 +// (subset: Hare's `(strs: str...)` blocks on task #16). +export fn concat(a: str, b: str) str = { + let total: i32 = a.len + b.len; + let buf: *u8 = os.alloc(total: u64): *u8; + let i: i32 = 0; + for (i < a.len) { buf[i] = a[i]; i += 1; }; + let j: i32 = 0; + for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; + let r: str; + r.ptr = buf; + r.len = total; + return r; +}; + +// sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is +// rune-wise; this ww form is byte-wise (no rune iterator yet, planned +// for commit 2). Clamps out-of-range silently where Hare aborts — +// retained for the existing getopt caller; will graduate when the +// rune-wise form lands. export fn sub(s: str, start: i32, end: i32) str = { let lo: i32 = start; let hi: i32 = end; @@ -1056,58 +1477,145 @@ export fn sub(s: str, start: i32, end: i32) str = { 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; }; +// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes) +// and return the borrowed slice trimmed to the encoded length. Hare +// inlines the same shape at ref/hare/strings/index.ha:132. +fn runebytes(scratch: []u8, r: rune) []u8 = { + let n: i32 = utf8.encoderune(scratch, r); + let s: []u8; + s.ptr = scratch.ptr; + s.len = n; + s.cap = n; + return s; +}; + +// hasprefix — true iff `in` begins with `prefix`. +// ref/hare/strings/suffix.ha:8. +export fn hasprefix(in: str, prefix: (str | rune)) bool = { + let scratch: [4]u8; + let p: []u8 = match (prefix) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hasprefix(toutf8(in), p); +}; + +// hassuffix — true iff `in` ends with `suff`. +// ref/hare/strings/suffix.ha:26. +export fn hassuffix(in: str, suff: (str | rune)) bool = { + let scratch: [4]u8; + let s: []u8 = match (suff) { + case let v: str => yield toutf8(v); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hassuffix(toutf8(in), s); +}; + +// byteindex — byte-wise offset of `needle` in `haystack`, or void if +// absent. ref/hare/strings/index.ha:127. Rune arm encodes via +// utf8.encoderune (Hare passes the encoded slice straight to +// bytes::index). +export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.index(toutf8(haystack), n); +}; + +// rbyteindex — byte-wise offset of the last `needle` in `haystack`. +// ref/hare/strings/index.ha:138. +export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.rindex(toutf8(haystack), n); +}; + +// contains — true iff `needle` occurs in `haystack`. +// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form +// `(needles: (str | rune)...)` blocks on task #16). +export fn contains(haystack: str, needle: (str | rune)) bool = { + match (byteindex(haystack, needle)) { + case let i: i32 => return true; + case void => return false; + }; + return false; +}; + +// trimprefix — `s` with `prefix` stripped from the front, or `s` +// unchanged if it doesn't start with `prefix`. Borrowed view. +// ref/hare/strings/trim.ha:60. +export fn trimprefix(input: str, prefix: str) str = { + if (!hasprefix(input, prefix)) { return input; }; let r: str; - r.ptr = s.ptr + (pre.len: u64); - r.len = s.len - pre.len; + r.ptr = input.ptr + (prefix.len: u64); + r.len = input.len - prefix.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; }; +// trimsuffix — symmetric. ref/hare/strings/trim.ha:69. +export fn trimsuffix(input: str, suffix: str) str = { + if (!hassuffix(input, suffix)) { return input; }; let r: str; - r.ptr = s.ptr; - r.len = s.len - suf.len; + r.ptr = input.ptr; + r.len = input.len - suffix.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 = { +// ltrim — strip occurrences of `exclude` (encoded as UTF-8) from the +// front. Borrowed view. ref/hare/strings/trim.ha:11 (subset: single +// rune; Hare's `(trim: rune...)` blocks on task #16). The no-rune +// strip-whitespace branch is omitted for the same reason. +export fn ltrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); let i: i32 = 0; - for (i < s.len) { - if (s[i] != c) { break; }; - i += 1; + for (i + pat.len <= input.len) { + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[i + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + i += pat.len; }; let r: str; - r.ptr = s.ptr + (i: u64); - r.len = s.len - i; + r.ptr = input.ptr + (i: u64); + r.len = input.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; +// rtrim — strip occurrences of `exclude` from the end. Borrowed view. +// ref/hare/strings/trim.ha:32 (same subset note). +export fn rtrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); + let n: i32 = input.len; + for (n >= pat.len) { + let off: i32 = n - pat.len; + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[off + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + n -= pat.len; }; let r: str; - r.ptr = s.ptr; + r.ptr = input.ptr; r.len = n; return r; }; -export fn trimbyte(s: str, c: u8) str = { - return rtrimbyte(ltrimbyte(s, c), c); +// trim — strip from both ends. ref/hare/strings/trim.ha:54. +export fn trim(input: str, exclude: rune) str = { + return ltrim(rtrim(input, exclude), exclude); }; // MODULE: strconv diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 78241e65..0a1400a3 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -747,56 +747,38 @@ export fn exists(path: str) bool = { return r >= 0i64; }; -// MODULE: strings -// strings — operations over the immutable str type ({ *u8, len }). -// Mirrors Hare's strings::; `len` and `is-empty` aren't functions -// (callers use `s.len` and `s.len == 0` directly). +// MODULE: bytes +// bytes — slice operations over []u8. Mirrors Hare's bytes module +// (ref/hare/bytes/) for the in-tree subset: search/equality/prefix +// helpers used by lib/encoding, lib/bufio, lib/memio. +// +// Documented divergences from Hare: +// - index_slice / rindex_slice use naive O(n·m); Hare specialises +// 2/3/4-byte needles and falls back to two_way (Crochemore-Perrin) +// for longer (ref/hare/bytes/index.ha:61, ref/hare/bytes/two_way.ha). +// Correctness equivalent. +// - contains takes a single needle; Hare's contains is variadic +// `(u8 | []u8)...` (ref/hare/bytes/contains.ha:5). No caller needs +// the variadic shape yet; graduate when one does. -use os; - -// compare — bytewise three-way comparison: negative if ab. Matches Hare's strings::compare. ASCII-order, not -// locale-aware. Callers that just need equality use `compare(a, b) == 0`. -export fn compare(a: str, b: str) i32 = { - let n: i32 = a.len; - if (b.len < n) { n = b.len; }; +// equal — true iff `a` and `b` have the same length and contents. +// ref/hare/bytes/equal.ha:9. +export fn equal(a: []u8, b: []u8) bool = { + if (a.len != b.len) { return false; }; let i: i32 = 0; - for (i < n) { - if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); }; - i += 1; - }; - return a.len - b.len; -}; - -export fn hasprefix(s: str, p: str) bool = { - if (p.len > s.len) { return false; }; - let i: i32 = 0; - for (i < p.len) { - if (s[i] != p[i]) { return false; }; + for (i < a.len) { + if (a[i] != b[i]) { return false; }; i += 1; }; return true; }; -export fn hassuffix(s: str, suf: str) 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; -}; - -// byteindex — first byte position of `needle` in `s`. Mirrors Hare's -// strings::byteindex: a single-codepoint rune scans for the byte that -// encodes it (ASCII only here — multi-byte UTF-8 awaits utf8 encode), -// a str needle scans for the substring. Returns void if absent. -export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { +// index — first offset of `needle` in `s`. u8 needle scans for the +// byte; []u8 needle scans for the substring. void if absent. +// ref/hare/bytes/index.ha:6. +export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { - case let r: rune => { - let c: u8 = r: u8; + case let c: u8 => { let i: i32 = 0; for (i < s.len) { if (s[i] == c) { return i; }; @@ -804,7 +786,7 @@ export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { }; return; }; - case let sub: str => { + case let sub: []u8 => { if (sub.len == 0) { return 0; }; if (sub.len > s.len) { return; }; let last: i32 = s.len - sub.len; @@ -825,87 +807,12 @@ export fn byteindex(s: str, needle: (str | rune)) (i32 | void) = { return; }; -// contains — true iff `sub` appears in `s`. Mirrors Hare's -// strings::contains shape (byte-wise on the str-needle case). -export fn contains(s: str, sub: str) bool = { - let r: (i32 | void) = byteindex(s, sub); - match (r) { - case let i: i32 => return true; - case void => return false; - }; - return false; -}; - -// concat — joins two strings into a fresh str. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::concat shape. -export fn concat(a: str, b: str) str = { - let total: i32 = a.len + b.len; - let buf: *u8 = os.alloc(total: u64): *u8; - let i: i32 = 0; - for (i < a.len) { buf[i] = a[i]; i += 1; }; - let j: i32 = 0; - for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; - let r: str; - r.ptr = buf; - r.len = total; - return r; -}; - -// dup — duplicate a string into a fresh allocation. Caller owns the -// returned str's storage; release via `os.free(r.ptr, r.len)`. Mirrors -// Hare's strings::dup shape — Hare returns `(str | nomem)`, ww doesn't -// have nomem (os.alloc aborts on OOM), so we return plain `str`. -// -// Empty input yields a `{nil, 0}` str — Hare returns the static empty -// string; same observable result. -export fn dup(s: str) str = { - let r: str; - r.ptr = nil; - r.len = 0; - if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; - let i: i32 = 0; - for (i < s.len) { buf[i] = s[i]; i += 1; }; - r.ptr = buf; - r.len = s.len; - return r; -}; - -// freeall — release every str element in `s` (those that were -// individually allocated) plus the slice's backing storage. Mirrors -// Hare's strings::freeall — the natural disposer for any function -// returning a fresh `[]str` of dup'd elements (e.g. shlex.split). -// -// Each element is freed via os.free at its own length; the slice -// header storage is freed at `cap * 16` bytes (one str = 16B). Empty -// elements (`{nil, 0}` from a zero-length dup) are skipped — calling -// os.free on a nil pointer at len 0 would tickle the rt_free guard -// that the runtime treats as a logic bug. -// -// `cap == 0` means the slice was never grown (empty `[]str` with no -// backing allocation); skip the header free in that case too. -export fn freeall(s: []str) void = { - let i: i32 = 0; - for (i < s.len) { - if (s[i].len > 0) { - os.free(s[i].ptr: *void, s[i].len: u64); - }; - i += 1; - }; - if (s.cap > 0) { - os.free(s.ptr: *void, (s.cap: u64) * 16u64); - }; -}; - -// rbyteindex — last byte position of `needle` in `s`. Mirrors Hare's -// strings::rbyteindex. Rune needle scans for the byte that encodes it -// (ASCII only); str needle scans for the substring. Empty str needle -// matches at s.len. -export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { +// rindex — last offset of `needle` in `s`. Empty []u8 needle returns +// s.len (ref/hare/bytes/index.ha:103 — Hare's loop yields r-0 at i=0). +// ref/hare/bytes/index.ha:86. +export fn rindex(s: []u8, needle: (u8 | []u8)) (i32 | void) = { match (needle) { - case let r: rune => { - let c: u8 = r: u8; + case let c: u8 => { let i: i32 = s.len - 1; for (i >= 0) { if (s[i] == c) { return i; }; @@ -913,7 +820,7 @@ export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { }; return; }; - case let sub: str => { + case let sub: []u8 => { if (sub.len == 0) { return s.len; }; if (sub.len > s.len) { return; }; let i: i32 = s.len - sub.len; @@ -933,9 +840,523 @@ export fn rbyteindex(s: str, needle: (str | rune)) (i32 | void) = { 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. +// contains — true iff `needle` (byte or sub-slice) appears in `s`. +// ref/hare/bytes/contains.ha:5 (variadic subset; see header note). +export fn contains(s: []u8, needle: (u8 | []u8)) bool = { + match (index(s, needle)) { + case let i: i32 => return true; + case void => return false; + }; + return false; +}; + +// hasprefix — true iff `s` starts with `pre`. +// ref/hare/bytes/contains.ha:21. +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 — true iff `s` ends with `suf`. +// ref/hare/bytes/contains.ha:35. +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`. ref/hare/bytes/reverse.ha:5. +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. ref/hare/bytes/zero.ha:5. +export fn zero(s: []u8) void = { + let i: i32 = 0; + for (i < s.len) { + s[i] = 0u8; + i += 1; + }; +}; + +// MODULE: utf8 +// encoding/utf8 — UTF-8 encode/decode. Hare port; see +// ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. +// +// The decoder is Hoehrmann's branchless DFA, originally published +// at . Hare's +// ref/hare/encoding/utf8/decodetable.ha:4 restructures Hoehrmann's +// flat table to 2D `[8][256]i8`; we flatten back to 1D `[2048]i8` +// because ww cgen does not yet ship 2D arrays (task #20). +// +// Surface deviation from ref/hare/encoding/utf8: +// +// - `encoderune` takes a caller-supplied `out: []u8` and returns +// the byte count. Hare returns a slice into a `static let buf`; +// the caller-buffer form mirrors lib/encoding/hex.encode and +// skips the static-buffer/slice-return pair. +// +// Deferred (no in-tree caller, follow-up tasks): `prev`, `slice`, +// `position`, `remaining`, `appendrune`, `strencode`, `strdecode`. +// Hare's string-iteration surface (`strings::iterator`/`strings::next` +// — ref/hare/strings/iter.ha) lives under lib/strings, not here. + +// ref/hare/encoding/utf8/types.ha:6 — incomplete trailing sequence. +// Plain `void` (not `!void`): a truncated tail is a control-flow +// signal, not an error caller can ignore. +export type more = void; + +// ref/hare/encoding/utf8/types.ha:9 — invalid UTF-8 sequence. +export type invalid = !void; + +// `done` is not a built-in singleton in ww (Hare ships it as part of +// the type system). Plain `void` (not `!void`): end-of-input is a +// continuation signal, not an error. lib/io spells its EOF the same +// way (lib/io/io.ww:8-11). +export type done = void; + +// ref/hare/encoding/utf8/decodetable.ha:4 — Hoehrmann's UTF-8 DFA, +// flat 1D `[2048]i8`. Layout: dfa[state*256 + byte] gives the next +// state (>0), the accept transition (0 — emit rune), or invalid (-1). +// Values match ref/hare/encoding/utf8/decodetable.ha verbatim. +let dfa: [2048]i8 = [ + // state 0 — initial byte: ASCII accepts (0), continuation/illegal + // byte rejects (-1), legal multibyte start emits a state. + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 3i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 4i8, 2i8, 2i8, + 5i8, 6i8, 6i8, 6i8, 7i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 1 — expecting one continuation byte (0x80..0xBF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, 0i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 2 — expecting one continuation byte (full 0x80..0xBF range). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 3 — first byte was 0xE0; continuation byte must be 0xA0..0xBF + // (rejects overlong 3-byte encodings). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 4 — first byte was 0xED; continuation byte must be 0x80..0x9F + // (rejects UTF-16 surrogate codepoints U+D800..U+DFFF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, 1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 5 — first byte was 0xF0; continuation byte must be 0x90..0xBF + // (rejects overlong 4-byte encodings). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 6 — middle continuation byte of a 4-byte sequence (0x80..0xBF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + + // state 7 — first byte was 0xF4; continuation byte must be 0x80..0x8F + // (rejects codepoints above U+10FFFF). + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, 2i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, + -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, -1i8, +]; + +// ref/hare/encoding/utf8/decode.ha:17 — payload-bit masks. Hare's +// [2][8]u8 flattened to 1D [16]u8; row 0 (offsets 0..7) is the +// continuation-byte mask (always 0x3F), row 1 (offsets 8..15) is the +// initial-byte payload mask indexed by the transition class. +let masks: [16]u8 = [ + 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, 0x3fu8, + 0x7fu8, 0x1fu8, 0x0fu8, 0x0fu8, 0x0fu8, 0x07u8, 0x07u8, 0x07u8, +]; + +// ref/hare/encoding/utf8/decode.ha:6 — incremental decoder state. +export type decoder = struct { + offs: i32, + src: []u8, +}; + +// ref/hare/encoding/utf8/decode.ha:12. +export fn decode(src: []u8) decoder = { + let d: decoder; + d.src = src; + d.offs = 0; + return d; +}; + +// ref/hare/encoding/utf8/decode.ha:27. Returns the next rune from a +// decoder, `done` at end-of-input, `more` on truncated trailing +// sequence, `invalid` on malformed input (overlong, surrogate, +// out-of-range, bad continuation). +// +// Algorithm is verbatim Hoehrmann (see file header). One structural +// rewrite: Hare encodes the "initial vs continuation byte" decision +// as the branchless `(state - 1): uint >> 31`, which assumes a 32-bit +// uint. ww's uint is 64-bit (cmd/wcc/type.c:58), so the shift answer +// would be 0x1_ffff_ffff rather than 1. We spell the same predicate +// with an explicit conditional. +export fn next(d: *decoder) (rune | done | more | invalid) = { + if (d.offs == d.src.len) { + let dn: done; return dn; + }; + let nx: i32 = 0; + let state: i32 = 0; + let r: u32 = 0u32; + for (d.offs < d.src.len) { + let b: u8 = d.src[d.offs]; + let bi: i32 = b: i32; + let row: i32 = state * 256 + bi; + let cell: i8 = dfa[row]; + nx = cell: i32; + let mi: i32 = 0; + if (state == 0) { mi = 1; }; + let m: u8 = masks[mi * 8 + (nx & 7)]; + r = (r << 6u32) | ((b & m): u32); + if (nx <= 0) { + d.offs += 1; + if (nx == 0) { return r: rune; }; + let e: invalid; return e; + }; + state = nx; + d.offs += 1; + }; + let mr: more; return mr; +}; + +// ref/hare/encoding/utf8/decode.ha:207. Strict whole-input check. +// The hot path: tight DFA loop, no rune assembly. Bails the moment +// the table returns -1 so malformed inputs don't pay for the rest +// of the buffer. +export fn validate(src: []u8) (void | invalid) = { + let state: i32 = 0; + let i: i32 = 0; + for (i < src.len) { + if (state < 0) { break; }; + let bi: i32 = src[i]: i32; + let cell: i8 = dfa[state * 256 + bi]; + state = cell: i32; + i += 1; + }; + if (state == 0) { return; }; + let e: invalid; return e; +}; + +// ref/hare/encoding/utf8/rune.ha:5. Encoded byte length of `r` as +// UTF-8. Callers in ww use this to size the buffer they hand to +// [[encoderune]]; values >0x10FFFF or negative are not legal Unicode +// codepoints and Hare aborts on them in `encoderune` itself, so we +// keep `runesz` infallible (matches Hare). +export fn runesz(r: rune) i32 = { + let ch: u32 = r: u32; + if (ch < 128u32) { return 1; }; + if (ch < 2048u32) { return 2; }; + if (ch < 65536u32) { return 3; }; + return 4; +}; + +// ref/hare/encoding/utf8/rune.ha:15. Expected byte length of the +// codepoint that starts with `c`, or `invalid` if `c` cannot start +// a legal UTF-8 sequence. Constants written in decimal because ww +// doesn't accept Hare's `0b1000_0000` binary syntax: 0x80=128, +// 0xC2=194, 0xE0=224, 0xF0=240, 0xF8=248. +export fn utf8sz(c: u8) (i32 | invalid) = { + if (c < 128u8) { return 1; }; + if (c < 194u8) { let e: invalid; return e; }; + if (c >= 248u8) { let e: invalid; return e; }; + if (c < 224u8) { return 2; }; + if (c < 240u8) { return 3; }; + return 4; +}; + +// ref/hare/encoding/utf8/encode.ha:7. Encode `r` into `out` (caller- +// supplied; must hold at least [[runesz]](r) bytes) and return the +// byte count. ABORT if `r` is a UTF-16 surrogate or above U+10FFFF — +// same precondition Hare asserts at ref/hare/encoding/utf8/encode.ha:9. +// +// Surface deviation: Hare returns `[]u8` (slice into a static buf). +// ww uses the caller-buffer form (matches lib/encoding/hex.encode); +// caller can reuse a [4]u8 stack scratch across encodes. +export fn encoderune(out: []u8, r: rune) i32 = { + let ch: u32 = r: u32; + if (ch >= 0xD800u32) { + if (ch <= 0xDFFFu32) { + abort("utf8.encoderune: surrogate codepoint"); + }; + }; + if (ch > 0x10FFFFu32) { + abort("utf8.encoderune: codepoint > U+10FFFF"); + }; + + let n: i32 = 0; + let first: u8 = 0u8; + if (ch < 0x80u32) { + first = 0u8; n = 1; + } else if (ch < 0x800u32) { + first = 0xC0u8; n = 2; + } else if (ch < 0x10000u32) { + first = 0xE0u8; n = 3; + } else { + first = 0xF0u8; n = 4; + }; + + let v: u32 = ch; + let i: i32 = n - 1; + for (i > 0) { + out[i] = ((v: u8) & 0x3Fu8) | 0x80u8; + v = v >> 6u32; + i -= 1; + }; + out[0] = (v: u8) | first; + return n; +}; + + +// MODULE: strings +// strings — operations over str ({ptr,len}). Hare port; see +// ref/hare/strings/. +// +// Documented divergences from Hare: +// +// - `concat(a, b)` is 2-arg. Hare ships `concat(strs: str...)` +// (ref/hare/strings/concat.ha:5). Blocks on task #16 (cstage +// variadic-pack drops .len of multi-field element type). Cite +// reverts on fix. +// - `trim` / `ltrim` / `rtrim` take a single rune. Hare's are +// `(exclude: rune...)` (ref/hare/strings/trim.ha:54). Same +// blocker as concat. Hare's no-rune branch (strip whitespace) +// is also dropped — depends on a rune set. +// - `contains` is non-variadic. Hare's is +// `contains(haystack, needles: (str | rune)...)` +// (ref/hare/strings/contains.ha:9). Same blocker. +// - `byteindex` / `rbyteindex` rune arms encode via +// `utf8.encoderune`; the legacy impls scanned for `r: u8` (an +// undocumented ASCII-only restriction that silently dropped +// to the wrong byte for U+80..U+7FF and higher). +// - `dup(s: str) str` — Hare returns `(str | nomem)`. ww's +// `os.alloc` aborts on OOM (no `nomem` type), so we return plain +// `str`. Empty input returns `{nil, 0}`; Hare returns the static +// empty string — same observable result. + +use bytes; +use utf8; +use os; + +// toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. +// `cap` equals `len`; the slice does not own a separate allocation. +export fn toutf8(s: str) []u8 = { + let r: []u8; + r.ptr = s.ptr; + r.len = s.len; + r.cap = s.len; + return r; +}; + +// fromutf8_unsafe — borrowed str view of `in`. Does not validate. +// ref/hare/strings/utf8.ha:10. +export fn fromutf8_unsafe(in: []u8) str = { + let r: str; + r.ptr = in.ptr; + r.len = in.len; + return r; +}; + +// compare — three-way bytewise codepoint-order comparison. +// ref/hare/strings/compare.ha:12. +export fn compare(a: str, b: str) i32 = { + let n: i32 = a.len; + if (b.len < n) { n = b.len; }; + let i: i32 = 0; + for (i < n) { + if (a[i] != b[i]) { return (a[i]: i32) - (b[i]: i32); }; + i += 1; + }; + return a.len - b.len; +}; + +// dup — allocate a fresh copy of `s`. Caller releases with +// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/dup.ha:7. +export fn dup(s: str) str = { + let r: str; + r.ptr = nil; + r.len = 0; + if (s.len == 0) { return r; }; + let buf: *u8 = os.alloc(s.len: u64): *u8; + let i: i32 = 0; + for (i < s.len) { buf[i] = s[i]; i += 1; }; + r.ptr = buf; + r.len = s.len; + return r; +}; + +// freeall — release each element + the slice header. The natural +// disposer for any `[]str` of dup'd elements (e.g. shlex.split). +// ref/hare/strings/dup.ha:38. +// +// Empty elements (`{nil, 0}` from a zero-length dup) are skipped: +// os.free on a nil pointer at len 0 tickles the rt_free guard. The +// slice header itself is freed at `cap * 16` (one str = 16B); a +// never-grown slice (cap == 0) skips the header free. +export fn freeall(s: []str) void = { + let i: i32 = 0; + for (i < s.len) { + if (s[i].len > 0) { + os.free(s[i].ptr: *void, s[i].len: u64); + }; + i += 1; + }; + if (s.cap > 0) { + os.free(s.ptr: *void, (s.cap: u64) * 16u64); + }; +}; + +// concat — fresh allocation containing `a` then `b`. Caller releases +// with `os.free(r.ptr, r.len: u64)`. ref/hare/strings/concat.ha:5 +// (subset: Hare's `(strs: str...)` blocks on task #16). +export fn concat(a: str, b: str) str = { + let total: i32 = a.len + b.len; + let buf: *u8 = os.alloc(total: u64): *u8; + let i: i32 = 0; + for (i < a.len) { buf[i] = a[i]; i += 1; }; + let j: i32 = 0; + for (j < b.len) { buf[a.len + j] = b[j]; j += 1; }; + let r: str; + r.ptr = buf; + r.len = total; + return r; +}; + +// sub — borrowed `s[start..end]`. ref/hare/strings/sub.ha:30 is +// rune-wise; this ww form is byte-wise (no rune iterator yet, planned +// for commit 2). Clamps out-of-range silently where Hare aborts — +// retained for the existing getopt caller; will graduate when the +// rune-wise form lands. export fn sub(s: str, start: i32, end: i32) str = { let lo: i32 = start; let hi: i32 = end; @@ -948,58 +1369,145 @@ export fn sub(s: str, start: i32, end: i32) str = { 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; }; +// runebytes — encode `r` into caller's `scratch` (must hold 4 bytes) +// and return the borrowed slice trimmed to the encoded length. Hare +// inlines the same shape at ref/hare/strings/index.ha:132. +fn runebytes(scratch: []u8, r: rune) []u8 = { + let n: i32 = utf8.encoderune(scratch, r); + let s: []u8; + s.ptr = scratch.ptr; + s.len = n; + s.cap = n; + return s; +}; + +// hasprefix — true iff `in` begins with `prefix`. +// ref/hare/strings/suffix.ha:8. +export fn hasprefix(in: str, prefix: (str | rune)) bool = { + let scratch: [4]u8; + let p: []u8 = match (prefix) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hasprefix(toutf8(in), p); +}; + +// hassuffix — true iff `in` ends with `suff`. +// ref/hare/strings/suffix.ha:26. +export fn hassuffix(in: str, suff: (str | rune)) bool = { + let scratch: [4]u8; + let s: []u8 = match (suff) { + case let v: str => yield toutf8(v); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.hassuffix(toutf8(in), s); +}; + +// byteindex — byte-wise offset of `needle` in `haystack`, or void if +// absent. ref/hare/strings/index.ha:127. Rune arm encodes via +// utf8.encoderune (Hare passes the encoded slice straight to +// bytes::index). +export fn byteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.index(toutf8(haystack), n); +}; + +// rbyteindex — byte-wise offset of the last `needle` in `haystack`. +// ref/hare/strings/index.ha:138. +export fn rbyteindex(haystack: str, needle: (str | rune)) (i32 | void) = { + let scratch: [4]u8; + let n: []u8 = match (needle) { + case let s: str => yield toutf8(s); + case let r: rune => yield runebytes(scratch[0:4], r); + }; + return bytes.rindex(toutf8(haystack), n); +}; + +// contains — true iff `needle` occurs in `haystack`. +// ref/hare/strings/contains.ha:9 (subset: Hare's variadic form +// `(needles: (str | rune)...)` blocks on task #16). +export fn contains(haystack: str, needle: (str | rune)) bool = { + match (byteindex(haystack, needle)) { + case let i: i32 => return true; + case void => return false; + }; + return false; +}; + +// trimprefix — `s` with `prefix` stripped from the front, or `s` +// unchanged if it doesn't start with `prefix`. Borrowed view. +// ref/hare/strings/trim.ha:60. +export fn trimprefix(input: str, prefix: str) str = { + if (!hasprefix(input, prefix)) { return input; }; let r: str; - r.ptr = s.ptr + (pre.len: u64); - r.len = s.len - pre.len; + r.ptr = input.ptr + (prefix.len: u64); + r.len = input.len - prefix.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; }; +// trimsuffix — symmetric. ref/hare/strings/trim.ha:69. +export fn trimsuffix(input: str, suffix: str) str = { + if (!hassuffix(input, suffix)) { return input; }; let r: str; - r.ptr = s.ptr; - r.len = s.len - suf.len; + r.ptr = input.ptr; + r.len = input.len - suffix.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 = { +// ltrim — strip occurrences of `exclude` (encoded as UTF-8) from the +// front. Borrowed view. ref/hare/strings/trim.ha:11 (subset: single +// rune; Hare's `(trim: rune...)` blocks on task #16). The no-rune +// strip-whitespace branch is omitted for the same reason. +export fn ltrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); let i: i32 = 0; - for (i < s.len) { - if (s[i] != c) { break; }; - i += 1; + for (i + pat.len <= input.len) { + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[i + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + i += pat.len; }; let r: str; - r.ptr = s.ptr + (i: u64); - r.len = s.len - i; + r.ptr = input.ptr + (i: u64); + r.len = input.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; +// rtrim — strip occurrences of `exclude` from the end. Borrowed view. +// ref/hare/strings/trim.ha:32 (same subset note). +export fn rtrim(input: str, exclude: rune) str = { + let scratch: [4]u8; + let pat: []u8 = runebytes(scratch[0:4], exclude); + let n: i32 = input.len; + for (n >= pat.len) { + let off: i32 = n - pat.len; + let j: i32 = 0; + let ok: bool = true; + for (j < pat.len) { + if (input[off + j] != pat[j]) { ok = false; j = pat.len; } + else { j += 1; }; + }; + if (!ok) { break; }; + n -= pat.len; }; let r: str; - r.ptr = s.ptr; + r.ptr = input.ptr; r.len = n; return r; }; -export fn trimbyte(s: str, c: u8) str = { - return rtrimbyte(ltrimbyte(s, c), c); +// trim — strip from both ends. ref/hare/strings/trim.ha:54. +export fn trim(input: str, exclude: rune) str = { + return ltrim(rtrim(input, exclude), exclude); }; // MODULE: strconv diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index e16918e1..275b7918 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -1748,8 +1748,14 @@ main(void) char tmpdir[64]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wwe2e_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); - snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", - tmpdir, bin, src); + /* -I lib/encoding/utf8 for any fixture that pulls in fmt / + * strconv / strings via `use` (transitive utf8.encoderune; + * task #17). Unused -I is benign for fixtures that don't. */ + char cwd700[1024]; + if (getcwd(cwd700, sizeof cwd700) == NULL) { fail++; continue; } + snprintf(cmd, sizeof cmd, + "cd %s && %s/ww build -I %s/lib/encoding/utf8 %s", + tmpdir, bin, cwd700, src); if (runwait(cmd) != 0) { fail++; continue; } char outbin[128]; diff --git a/test/wcc/966_strings_run.c b/test/wcc/966_strings_run.c new file mode 100644 index 00000000..e41ef831 --- /dev/null +++ b/test/wcc/966_strings_run.c @@ -0,0 +1,55 @@ +/* + * 966_strings_run — execute the lib/strings @test fixture under the + * C-side `ww run` driver and assert exit 0. + * + * Same thin-wrapper shape as 967_bytes_run / 968_utf8_run / 979_hex_run: + * stringstest.ww carries its own `export fn main()` that drives the + * @test fns and signals which case failed via the exit code. + * + * -I lib/encoding/utf8 is required because lib/strings.byteindex + * encodes the rune-needle arm via utf8.encoderune; the import resolver + * doesn't yet walk encoding/ subdirs (task #17). + */ +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return 1; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + + const char *src = "lib/strings/stringstest.ww"; + char path[1024], cmd[2048]; + snprintf(path, sizeof path, "%s/%s", cwd, src); + snprintf(cmd, sizeof cmd, + "%s/ww run -I %s/lib/encoding/utf8 %s", + bin, cwd, path); + int rc = runwait(cmd); + if (rc != 0) { + fprintf(stderr, "strings_run FAIL: %s exited %d\n", src, rc); + return 1; + } + printf("strings_run: %s ok\n", src); + return 0; +} diff --git a/test/wcc/970_fmt_run.c b/test/wcc/970_fmt_run.c index cb2b473d..34deac3f 100644 --- a/test/wcc/970_fmt_run.c +++ b/test/wcc/970_fmt_run.c @@ -41,7 +41,9 @@ main(void) const char *src = "lib/fmt/fmttest.ww"; char path[1024], cmd[2048]; snprintf(path, sizeof path, "%s/%s", cwd, src); - snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path); + /* -I lib/encoding/utf8 — task #17 */ + snprintf(cmd, sizeof cmd, + "%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path); int rc = runwait(cmd); if (rc != 0) { fprintf(stderr, "fmt_run FAIL: %s exited %d\n", src, rc); diff --git a/test/wcc/971_log_run.c b/test/wcc/971_log_run.c index 44c06dbc..19cacef3 100644 --- a/test/wcc/971_log_run.c +++ b/test/wcc/971_log_run.c @@ -41,7 +41,9 @@ main(void) const char *src = "lib/log/logtest.ww"; char path[1024], cmd[2048]; snprintf(path, sizeof path, "%s/%s", cwd, src); - snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path); + /* -I lib/encoding/utf8 — task #17 */ + snprintf(cmd, sizeof cmd, + "%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path); int rc = runwait(cmd); if (rc != 0) { fprintf(stderr, "log_run FAIL: %s exited %d\n", src, rc); diff --git a/test/wcc/972_fnmatch_run.c b/test/wcc/972_fnmatch_run.c index 57dd2bdf..829ecfdd 100644 --- a/test/wcc/972_fnmatch_run.c +++ b/test/wcc/972_fnmatch_run.c @@ -41,7 +41,9 @@ main(void) const char *src = "lib/fnmatch/fnmatchtest.ww"; char path[1024], cmd[2048]; snprintf(path, sizeof path, "%s/%s", cwd, src); - snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path); + /* -I lib/encoding/utf8 — task #17 */ + snprintf(cmd, sizeof cmd, + "%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path); int rc = runwait(cmd); if (rc != 0) { fprintf(stderr, "fnmatch_run FAIL: %s exited %d\n", src, rc); diff --git a/test/wcc/982_getopt_run.c b/test/wcc/982_getopt_run.c index 9b128084..99771a98 100644 --- a/test/wcc/982_getopt_run.c +++ b/test/wcc/982_getopt_run.c @@ -40,7 +40,10 @@ main(void) const char *src = "lib/getopt/getopttest.ww"; char path[1024], cmd[2048]; snprintf(path, sizeof path, "%s/%s", cwd, src); - snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path); + /* -I lib/encoding/utf8: getopt -> strings -> utf8.encoderune + * (task #17 — resolver doesn't yet walk encoding/ subdirs). */ + snprintf(cmd, sizeof cmd, + "%s/ww run -I %s/lib/encoding/utf8 %s", bin, cwd, path); int rc = runwait(cmd); if (rc != 0) { fprintf(stderr, "getopt_run FAIL: %s exited %d\n", src, rc); diff --git a/test/wcc/990_selfhost.c b/test/wcc/990_selfhost.c index 41aeaf1f..12df823c 100644 --- a/test/wcc/990_selfhost.c +++ b/test/wcc/990_selfhost.c @@ -99,9 +99,11 @@ probe_smoke(const char *bin) snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsh_%d", getpid()); mkdir(tmpdir, 0755); char cmd[2048]; + /* -I lib/encoding/utf8: smoke.ww uses strconv -> strings -> + * utf8.encoderune (task #17 — resolver doesn't walk encoding/). */ snprintf(cmd, sizeof cmd, - "cd %s && %s/ww build %s/selfhost/test/smoke.ww >/dev/null 2>&1", - tmpdir, bin, cwd); + "cd %s && %s/ww build -I %s/lib/encoding/utf8 %s/selfhost/test/smoke.ww >/dev/null 2>&1", + tmpdir, bin, cwd, cwd); if (runwait(cmd) != 0) { fprintf(stderr, "smoke FAIL: ww build did not succeed\n"); return -1; @@ -743,8 +745,8 @@ probe_ww_links(const char *bin) runwait(cmd); /* ww build to get the .combined.ww as a side effect. */ snprintf(cmd, sizeof cmd, - "cd %s && %s/ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1", - tmpdir, bin, cwd, cwd, cwd, cwd, tmpsrc); + "cd %s && %s/ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/lib/encoding/utf8 -I %s/selfhost/cmd/wcc %s >/dev/null 2>&1", + tmpdir, bin, cwd, cwd, cwd, cwd, cwd, tmpsrc); if (runwait(cmd) != 0) { fprintf(stderr, "ww-links FAIL: ww build %s\n", fix); fail++; diff --git a/test/wcc/993_ww_ww.c b/test/wcc/993_ww_ww.c index cb39eeea..494d8173 100644 --- a/test/wcc/993_ww_ww.c +++ b/test/wcc/993_ww_ww.c @@ -150,8 +150,8 @@ main(void) static char wwdump_src[2048], wwdump_incs[4096]; snprintf(wwdump_src, sizeof wwdump_src, "%s/selfhost/cmd/wwdump/main.ww", cwd); snprintf(wwdump_incs, sizeof wwdump_incs, - "%s/lib/ww:%s/lib/ww/lex:%s/lib/ww/parse:%s/selfhost/cmd/wcc", - cwd, cwd, cwd, cwd); + "%s/lib/ww:%s/lib/ww/lex:%s/lib/ww/parse:%s/lib/encoding/utf8:%s/selfhost/cmd/wcc", + cwd, cwd, cwd, cwd, cwd); cases[1].src = wwdump_src; cases[1].incs = wwdump_incs; diff --git a/test/wcc/995_self_rebuild.c b/test/wcc/995_self_rebuild.c index 4e7dcb71..65dae848 100644 --- a/test/wcc/995_self_rebuild.c +++ b/test/wcc/995_self_rebuild.c @@ -62,6 +62,9 @@ slurp_eq(const char *a, const char *b) /* Each tool builds via `ww_ww build -I -I lib/ww -I selfhost/cmd/wcc src`. * lib/ww holds the language introspection (lex/tok/ast/parse/typ/sym); * selfhost/cmd/wcc holds the compiler internals (mem/check/cgen*). + * lib/encoding/utf8 carries the rune codec strings.byteindex needs; + * the import resolver doesn't yet walk encoding/ subdirs (task #17), + * so the dep travels as an explicit -I until it does. * Some tools have a local module dir (w6a, w6l with sibling .ww files). * inc_local is "" for tools without one (w6c, ww, wwdump). */ @@ -77,14 +80,14 @@ rebuild_one(const char *bin, const char *cwd, const char *tool, if (inc_local && inc_local[0]) { snprintf(cmd, sizeof cmd, - "cd %s && %s/ww_ww build -I %s/%s -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc " + "cd %s && %s/ww_ww build -I %s/%s -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/lib/encoding/utf8 -I %s/selfhost/cmd/wcc " "%s/%s >/dev/null 2>&1", - workdir, bin, cwd, inc_local, cwd, cwd, cwd, cwd, cwd, src_rel); + workdir, bin, cwd, inc_local, cwd, cwd, cwd, cwd, cwd, cwd, src_rel); } else { snprintf(cmd, sizeof cmd, - "cd %s && %s/ww_ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/selfhost/cmd/wcc " + "cd %s && %s/ww_ww build -I %s/lib/ww -I %s/lib/ww/lex -I %s/lib/ww/parse -I %s/lib/encoding/utf8 -I %s/selfhost/cmd/wcc " "%s/%s >/dev/null 2>&1", - workdir, bin, cwd, cwd, cwd, cwd, cwd, src_rel); + workdir, bin, cwd, cwd, cwd, cwd, cwd, cwd, src_rel); } if (runwait(cmd) != 0) { fprintf(stderr, "self-rebuild FAIL: ww_ww build errored on %s\n", tool);