// ascii — rune-class predicates and case folding for the ASCII range. // Matches Hare's ascii::isdigit family (rune-taking signature). Runes // outside 0..127 always answer `false`. The lexer hot path uses these // inline; they are expected to inline to a couple of compares. package ascii; import strings; export fn isdigit(c: rune) bool = { if (c < '0') { return false; }; if (c > '9') { return false; }; return true; }; export fn isupper(c: rune) bool = { if (c < 'A') { return false; }; if (c > 'Z') { return false; }; return true; }; export fn islower(c: rune) bool = { if (c < 'a') { return false; }; if (c > 'z') { return false; }; return true; }; export fn isalpha(c: rune) bool = { if (isupper(c)) { return true; }; return islower(c); }; export fn isalnum(c: rune) bool = { if (isalpha(c)) { return true; }; return isdigit(c); }; // isspace — the C/Hare set: space, tab, NL, VT, FF, CR. export fn isspace(c: rune) bool = { if (c == ' ') { return true; }; if (c == '\t') { return true; }; if (c == '\n') { return true; }; if (c == '\v') { return true; }; if (c == '\f') { return true; }; if (c == '\r') { return true; }; return false; }; export fn isxdigit(c: rune) bool = { if (isdigit(c)) { return true; }; if (c >= 'A') { if (c <= 'F') { return true; }; }; if (c >= 'a') { if (c <= 'f') { return true; }; }; return false; }; // valid — `c` is in the 0..127 ASCII range. export fn valid(c: rune) bool = { if (c < 0) { return false; }; if (c > 127) { return false; }; return true; }; // validstr — every byte in `s` is ASCII (0..127). export fn validstr(s: str) bool = { let i: i32 = 0; for (i < s.len) { // High-bit test rather than `> 127u8`; both cgens lower // the bitwise form identically. The `> u8` form picks // JA vs JG depending on signed/unsigned dispatch. if ((s[i] & 128u8) != 0u8) { return false; }; i += 1; }; return true; }; // iscntrl — control chars: 0..31 and 127. export fn iscntrl(c: rune) bool = { if (c >= 0) { if (c <= 31) { return true; }; }; if (c == 127) { return true; }; return false; }; // isblank — space and tab. export fn isblank(c: rune) bool = { if (c == ' ') { return true; }; if (c == '\t') { return true; }; return false; }; // isprint — printable: space through '~'. export fn isprint(c: rune) bool = { if (c < ' ') { return false; }; if (c > '~') { return false; }; return true; }; // isgraph — printable, non-space. export fn isgraph(c: rune) bool = { if (c < '!') { return false; }; if (c > '~') { return false; }; return true; }; // ispunct — printable, non-alnum, non-space. export fn ispunct(c: rune) bool = { if (!isgraph(c)) { return false; }; if (isalnum(c)) { return false; }; return true; }; // tolower / toupper — fold ASCII case. Non-letters pass through. export fn tolower(c: rune) rune = { if (isupper(c)) { return c + 32; }; return c; }; export fn toupper(c: rune) rune = { if (islower(c)) { return c - 32; }; return c; }; // strcasecmp — three-way ASCII case-insensitive compare. export fn strcasecmp(a: str, b: str) i32 = { let n: i32 = a.len; if (b.len < n) { n = b.len; }; let i: i32 = 0; for (i < n) { let ca: rune = tolower(a[i]: rune); let cb: rune = tolower(b[i]: rune); if (ca != cb) { return (ca - cb): i32; }; i += 1; }; return a.len - b.len; }; // strlower — ASCII-lowercased copy of s, newly allocated. // ref/hare/ascii/string.ha:11. export fn strlower(s: str) (str | nomem) = { // empty bypass: ww alloc([],0) routes through nomem; Hare allocs 0 // and zero-loops (ref/hare/ascii/string.ha:12). if (s.len == 0) { let r: str; r.ptr = nil; r.len = 0; return r; }; let buf: []u8 = alloc([], s.len: u64)?; return strlower_buf(s, buf); }; // strlower_buf — ASCII-lowercase s into buf (overwrites). nomem if buf // too small. ref/hare/ascii/string.ha:21. // Byte-wise fold: ASCII case-fold only touches bytes <0x80; UTF-8 // multibyte bytes are >=0x80 and pass through unchanged, so byte-wise // equals Hare's rune fold and is length-preserving. // ww uses an explicit `buf.cap < s.len` check + `let nm: nomem` value // because it has no static-append builtin; Hare reaches the same // nomem-on-too-small via `static append(buf, ...)?` (string.ha:25). export fn strlower_buf(s: str, buf: []u8) (str | nomem) = { if (buf.cap < s.len) { let nm: nomem; return nm; }; let i: i32 = 0; for (i < s.len) { buf.ptr[i] = tolower(s[i]: rune): u8; i += 1; }; buf.len = s.len; return strings.frombytes(buf); }; // strupper — ASCII-uppercased copy of s, newly allocated. // ref/hare/ascii/string.ha:33. export fn strupper(s: str) (str | nomem) = { if (s.len == 0) { let r: str; r.ptr = nil; r.len = 0; return r; }; let buf: []u8 = alloc([], s.len: u64)?; return strupper_buf(s, buf); }; // strupper_buf — see strlower_buf. ref/hare/ascii/string.ha:43. export fn strupper_buf(s: str, buf: []u8) (str | nomem) = { if (buf.cap < s.len) { let nm: nomem; return nm; }; let i: i32 = 0; for (i < s.len) { buf.ptr[i] = toupper(s[i]: rune): u8; i += 1; }; buf.len = s.len; return strings.frombytes(buf); };