std1.0.0-beta-7 SafeC docs

Source collections/string.sc

View in the repository

// SafeC Standard Library — String implementation
#pragma once
#include <std/collections/string.h>
#include <std/mem.h>
#include <std/str.h>
#include <std/fmt.h>
#include <std/convert.h>

// ── Internal: ensure at least `need` bytes of capacity (not counting NUL) ─────
namespace std {

inline int String::reserve_(unsigned long need) {
    unsigned long needed_cap = need + 1UL;
    if (needed_cap <= self.cap) { return 1; }
    unsigned long new_cap = self.cap < 16UL ? 16UL : self.cap;
    while (new_cap < needed_cap) { new_cap = new_cap * 2UL; }
    unsafe {
        unsigned char* raw = realloc_buf_raw_((unsigned char*)self.data, new_cap);
        if (raw == (unsigned char*)0) { return 0; }
        self.data = (&heap char)raw;
        self.cap  = new_cap;
    }
    return 1;
}

// ── Lifecycle ─────────────────────────────────────────────────────────────────
inline struct String String::new() {
    struct String s;
    s.len = 0UL;
    unsafe {
        unsigned char* raw = alloc_raw_(16UL);
        if (raw == (unsigned char*)0) {
            s.data = (&heap char)0;
            s.cap = 0UL;
            return s;
        }
        s.data = (&heap char)raw;
    }
    unsafe { s.data[0] = (char)0; }
    s.cap = 16UL;
    return s;
}

inline struct String String::from(const &char cstr) {
    struct String s = String::new();
    if (cstr != (const char*)0) { s.push(cstr); }
    return s;
}

inline struct String String::from_n_(const &char cstr, unsigned long n) {
    struct String s;
    s.len = 0UL;
    unsafe {
        unsigned char* raw = alloc_raw_(n + 1UL);
        if (raw == (unsigned char*)0) {
            s.data = (&heap char)0;
            s.cap = 0UL;
            return s;
        }
        s.data = (&heap char)raw;
        s.cap = n + 1UL;
        safe_memcpy_raw_((unsigned char*)s.data, (const unsigned char*)cstr, n);
    }
    unsafe { s.data[n] = (char)0; }
    s.len = n;
    return s;
}

inline struct String String::with_cap(unsigned long cap) {
    struct String s;
    s.len = 0UL;
    s.cap = cap + 1UL;
    unsafe {
        unsigned char* raw = alloc_raw_(s.cap);
        if (raw == (unsigned char*)0) {
            s.data = (&heap char)0;
            s.cap = 0UL;
            return s;
        }
        s.data = (&heap char)raw;
    }
    unsafe { s.data[0] = (char)0; }
    return s;
}

inline struct String String::repeat_str(const &char cstr, unsigned long n) {
    unsigned long slen  = str_len(cstr);
    unsigned long total = slen * n;
    struct String s = String::with_cap(total);
    unsigned long i = 0UL;
    while (i < n) { s.push(cstr); i = i + 1UL; }
    return s;
}

inline struct String String::join(const ?&char sep, []String parts) {
    struct String out = String::new();
    unsigned long i = 0UL;
    while (i < parts.len) {
        if (i > 0UL) {
            match (sep) { case some(s): out.push(s); case null: { } }
        }
        out.push_str(&parts[i]);
        i = i + 1UL;
    }
    return out;
}

inline struct String String::from_bytes([]const char bytes) {
    if (bytes.len == 0UL) return String::new();
    unsafe { return String::from_n_((const char*)bytes.ptr, bytes.len); }
}

inline int String::push_bytes([]const char bytes) {
    if (bytes.len == 0UL) return 1;
    unsafe { return self.push_n_((const char*)bytes.ptr, bytes.len); }
}

inline []const char String::as_bytes() const {
    unsafe { return ((const char*)self.data)[0..self.len]; }
}

// kept for std/ml until its refactor moves to the static method
inline struct String string_new() { return String::new(); }
inline struct String string_from(const &char cstr) { return String::from(cstr); }
inline struct String string_from_n_(const &char cstr, unsigned long n) { return String::from_n_(cstr, n); }

inline struct String String::clone() const {
    unsafe { return String::from_n_((const char*)self.data, self.len); }
}

inline void String::free() { self.drop(); }

inline void String::drop() {
    unsafe {
        if (self.cap > 0UL) { dealloc(self.data); }
        self.data = (&heap char)0;
    }
    self.len  = 0UL;
    self.cap  = 0UL;
}

// ── Access ────────────────────────────────────────────────────────────────────
unsigned long String::length() const  { return self.len; }
int           String::is_empty() const { return self.len == 0UL; }

inline const &char String::as_ptr() const {
    return self.data;
}

inline int String::char_at(unsigned long idx) const {
    if (idx >= self.len) { return -1; }
    unsafe { return (int)(unsigned char)self.data[idx]; }
}

inline void String::set_char(unsigned long idx, char c) {
    if (idx < self.len) { unsafe { self.data[idx] = c; } }
}

// ── Capacity ──────────────────────────────────────────────────────────────────
inline int String::reserve(unsigned long additional) {
    return self.reserve_(self.len + additional);
}

inline void String::shrink_to_fit() {
    if (self.len + 1UL >= self.cap) { return; }
    unsigned long new_cap = self.len + 1UL;
    unsafe {
        unsigned char* raw = realloc_buf_raw_((unsigned char*)self.data, new_cap);
        if (raw == (unsigned char*)0) { return; }
        self.data = (&heap char)raw;
        self.cap  = new_cap;
    }
}

// ── Append ────────────────────────────────────────────────────────────────────
inline int String::push_char(char c) {
    if (!self.reserve_(self.len + 1UL)) { return 0; }
    unsafe { self.data[self.len] = c; }
    self.len = self.len + 1UL;
    unsafe { self.data[self.len] = (char)0; }
    return 1;
}

inline int String::push(const &char cstr) {
    if (cstr == (const char*)0) { return 1; }
    unsigned long clen = str_len(cstr);
    if (!self.reserve_(self.len + clen)) { return 0; }
    unsafe { safe_memcpy_raw_((unsigned char*)((char*)self.data + self.len), (const unsigned char*)cstr, clen + 1UL); }
    self.len = self.len + clen;
    return 1;
}

inline int String::push_n_(const &char data, unsigned long n) {
    if (n == 0UL) { return 1; }
    if (!self.reserve_(self.len + n)) { return 0; }
    unsafe { safe_memcpy_raw_((unsigned char*)((char*)self.data + self.len), (const unsigned char*)data, n); }
    self.len = self.len + n;
    unsafe { self.data[self.len] = (char)0; }
    return 1;
}

inline int String::push_str(&String other) {
    unsafe { return self.push((const char*)other.data); }
}

inline int String::push_int(long long v) {
    char buf[32];
    fmt_int(buf[0..32], v);
    return self.push(&buf[0]);
}

inline int String::push_uint(unsigned long long v) {
    char buf[32];
    fmt_uint(buf[0..32], v);
    return self.push(&buf[0]);
}

inline int String::push_float(double v, int decimals) {
    char buf[64];
    fmt_float(buf[0..64], v, decimals);
    return self.push(&buf[0]);
}

inline int String::push_bool(int v) {
    return self.push(v ? "true" : "false");
}

// ── Modification ──────────────────────────────────────────────────────────────
inline void String::clear() {
    self.len = 0UL;
    if (self.cap > 0UL) { unsafe { self.data[0] = (char)0; } }
}

inline void String::truncate(unsigned long new_len) {
    if (new_len >= self.len) { return; }
    self.len = new_len;
    if (self.cap > 0UL) { unsafe { self.data[new_len] = (char)0; } }
}

inline int String::insert(unsigned long idx, const &char cstr) {
    if (idx > self.len) { idx = self.len; }
    unsigned long clen = str_len(cstr);
    if (!self.reserve_(self.len + clen)) { return 0; }
    unsafe {
        safe_memmove_raw_((unsigned char*)((char*)self.data + idx + clen),
                     (const unsigned char*)((char*)self.data + idx), self.len - idx + 1UL);
        safe_memcpy_raw_((unsigned char*)((char*)self.data + idx), (const unsigned char*)cstr, clen);
    }
    self.len = self.len + clen;
    return 1;
}

inline int String::remove_range(unsigned long start, unsigned long end) {
    if (start >= self.len || start >= end) { return 0; }
    if (end > self.len) { end = self.len; }
    unsigned long removed = end - start;
    unsafe {
        safe_memmove_raw_((unsigned char*)((char*)self.data + start),
                     (const unsigned char*)((char*)self.data + end), self.len - end + 1UL);
    }
    self.len = self.len - removed;
    return 1;
}

inline void String::replace_char(char from, char to) {
    unsigned long i = 0UL;
    while (i < self.len) {
        unsafe { if (self.data[i] == from) { self.data[i] = to; } }
        i = i + 1UL;
    }
}

inline int String::replace(const &char from, const &char to) {
    long long pos = self.index_of(from);
    if (pos < 0LL) { return 0; }
    unsigned long from_len = str_len(from);
    self.remove_range((unsigned long)pos, (unsigned long)pos + from_len);
    self.insert((unsigned long)pos, to);
    return 1;
}

// Single left-to-right pass into a freshly-sized buffer — O(n). The
// previous implementation called index_of() (a full O(n) scan from the
// *start* of the string, not from the current position) once per match,
// then shifted the whole remaining tail via remove_range()+insert() for
// each one — O(matches * n) overall, quadratic for a string with many
// matches.
inline int String::replace_all(const &char from, const &char to) {
    unsigned long from_len = str_len(from);
    if (from_len == 0UL) { return 0; }
    unsigned long to_len = str_len(to);

    // Count non-overlapping matches first, so the replacement buffer can
    // be allocated exactly once at its final size.
    int cnt = 0;
    unsigned long i = 0UL;
    unsafe {
        while (i + from_len <= self.len) {
            if (safe_memcmp_raw_((const unsigned char*)((char*)self.data + i), (const unsigned char*)from, from_len) == 0) {
                cnt = cnt + 1;
                i = i + from_len;
            } else {
                i = i + 1UL;
            }
        }
    }
    if (cnt == 0) { return 0; }

    unsigned long newLen = self.len - (unsigned long)cnt * from_len + (unsigned long)cnt * to_len;
    unsafe {
        char* newBuf = (char*)alloc_raw_(newLen + 1UL);
        if (newBuf == (char*)0) { return 0; }

        unsigned long src = 0UL;
        unsigned long dst = 0UL;
        while (src < self.len) {
            if (src + from_len <= self.len &&
                safe_memcmp_raw_((const unsigned char*)((char*)self.data + src), (const unsigned char*)from, from_len) == 0) {
                if (to_len > 0UL) {
                    safe_memcpy_raw_((unsigned char*)(newBuf + dst), (const unsigned char*)to, to_len);
                }
                dst = dst + to_len;
                src = src + from_len;
            } else {
                newBuf[dst] = self.data[src];
                dst = dst + 1UL;
                src = src + 1UL;
            }
        }
        newBuf[dst] = (char)0;

        if (self.data != (&heap char)0) { dealloc(self.data); }
        self.data = (&heap char)newBuf;
        self.len  = newLen;
        self.cap  = newLen + 1UL;
    }
    return cnt;
}

inline void String::reverse() {
    if (self.len < 2UL) { return; }
    unsigned long lo = 0UL;
    unsigned long hi = self.len - 1UL;
    while (lo < hi) {
        unsafe {
            char tmp = self.data[lo];
            self.data[lo] = self.data[hi];
            self.data[hi] = tmp;
        }
        lo = lo + 1UL;
        hi = hi - 1UL;
    }
}

inline int String::pop_char() {
    if (self.len == 0UL) { return -1; }
    self.len = self.len - 1UL;
    unsafe {
        int c = (int)(unsigned char)self.data[self.len];
        self.data[self.len] = (char)0;
        return c;
    }
}

// ── Search ────────────────────────────────────────────────────────────────────
inline long long String::index_of(const &char needle) const {
    if (needle == (const char*)0 || self.cap == 0UL) { return -1LL; }
    unsafe {
        const char* found = str_find((const char*)self.data, needle);
        if (found == (const char*)0) { return -1LL; }
        return (long long)(found - (const char*)self.data);
    }
}

inline long long String::last_index_of(const &char needle) const {
    if (needle == (const char*)0 || self.cap == 0UL) { return -1LL; }
    unsigned long nlen = str_len(needle);
    if (nlen == 0UL) { return (long long)self.len; }
    long long result = -1LL;
    unsigned long i = 0UL;
    while (i + nlen <= self.len) {
        unsafe {
            if (safe_memcmp_raw_((const unsigned char*)((char*)self.data + i), (const unsigned char*)needle, nlen) == 0) {
                result = (long long)i;
            }
        }
        i = i + 1UL;
    }
    return result;
}

inline int String::contains(const &char needle) const {
    return self.index_of(needle) >= 0LL;
}

inline int String::starts_with(const &char prefix) const {
    unsigned long plen = str_len(prefix);
    if (plen > self.len) { return 0; }
    unsafe { return safe_memcmp_raw_((const unsigned char*)self.data, (const unsigned char*)prefix, plen) == 0; }
}

inline int String::ends_with(const &char suffix) const {
    unsigned long slen = str_len(suffix);
    if (slen > self.len) { return 0; }
    unsafe {
        return safe_memcmp_raw_((const unsigned char*)((char*)self.data + self.len - slen),
                           (const unsigned char*)suffix, slen) == 0;
    }
}

inline int String::count(const &char needle) const {
    unsigned long nlen = str_len(needle);
    if (nlen == 0UL) { return 0; }
    int cnt = 0;
    unsigned long i = 0UL;
    while (i + nlen <= self.len) {
        unsafe {
            if (safe_memcmp_raw_((const unsigned char*)((char*)self.data + i), (const unsigned char*)needle, nlen) == 0) {
                cnt = cnt + 1;
                i = i + nlen;
            } else {
                i = i + 1UL;
            }
        }
    }
    return cnt;
}

inline long long String::find_char(char c) const {
    unsigned long i = 0UL;
    while (i < self.len) {
        unsafe { if (self.data[i] == c) { return (long long)i; } }
        i = i + 1UL;
    }
    return -1LL;
}

inline long long String::rfind_char(char c) const {
    if (self.len == 0UL) { return -1LL; }
    unsigned long i = self.len;
    while (i > 0UL) {
        i = i - 1UL;
        unsafe { if (self.data[i] == c) { return (long long)i; } }
    }
    return -1LL;
}

// ── Transformation ────────────────────────────────────────────────────────────
inline struct String String::substr(unsigned long start, unsigned long end) const {
    if (start >= self.len) { return String::new(); }
    if (end > self.len) { end = self.len; }
    if (start >= end) { return String::new(); }
    unsafe { return String::from_n_((const char*)((char*)self.data + start), end - start); }
}

inline struct String String::to_upper() const {
    struct String out = self.clone();
    unsigned long i = 0UL;
    while (i < out.len) {
        unsafe {
            char c = out.data[i];
            if (c >= 'a' && c <= 'z') { out.data[i] = (char)(c - (char)32); }
        }
        i = i + 1UL;
    }
    return out;
}

inline struct String String::to_lower() const {
    struct String out = self.clone();
    unsigned long i = 0UL;
    while (i < out.len) {
        unsafe {
            char c = out.data[i];
            if (c >= 'A' && c <= 'Z') { out.data[i] = (char)(c + (char)32); }
        }
        i = i + 1UL;
    }
    return out;
}

inline struct String String::trim_left() const {
    unsigned long start = 0UL;
    while (start < self.len) {
        char c;
        unsafe { c = self.data[start]; }
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { start = start + 1UL; }
        else { break; }
    }
    unsafe { return String::from_n_((const char*)((char*)self.data + start), self.len - start); }
}

inline struct String String::trim_right() const {
    unsigned long end = self.len;
    while (end > 0UL) {
        char c;
        unsafe { c = self.data[end - 1UL]; }
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { end = end - 1UL; }
        else { break; }
    }
    unsafe { return String::from_n_((const char*)self.data, end); }
}

inline struct String String::trim() const {
    struct String tmp = self.trim_left();
    struct String out = tmp.trim_right();
    tmp.free();
    return out;
}

inline struct String String::pad_left(unsigned long width, char fill) const {
    if (self.len >= width) { return self.clone(); }
    unsigned long pad = width - self.len;
    struct String out = String::with_cap(width);
    unsigned long i = 0UL;
    while (i < pad) { out.push_char(fill); i = i + 1UL; }
    unsafe { out.push((const char*)self.data); }
    return out;
}

inline struct String String::pad_right(unsigned long width, char fill) const {
    if (self.len >= width) { return self.clone(); }
    unsigned long pad = width - self.len;
    struct String out = String::with_cap(width);
    unsafe { out.push((const char*)self.data); }
    unsigned long i = 0UL;
    while (i < pad) { out.push_char(fill); i = i + 1UL; }
    return out;
}

inline struct String String::strip_prefix(const &char prefix) const {
    unsigned long plen = str_len(prefix);
    if (plen > self.len) { return self.clone(); }
    unsafe {
        if (safe_memcmp_raw_((const unsigned char*)self.data, (const unsigned char*)prefix, plen) != 0) {
            return self.clone();
        }
        return String::from_n_((const char*)((char*)self.data + plen), self.len - plen);
    }
}

inline struct String String::strip_suffix(const &char suffix) const {
    unsigned long slen = str_len(suffix);
    if (slen > self.len) { return self.clone(); }
    unsafe {
        if (safe_memcmp_raw_((const unsigned char*)((char*)self.data + self.len - slen), (const unsigned char*)suffix, slen) != 0) {
            return self.clone();
        }
        return String::from_n_((const char*)self.data, self.len - slen);
    }
}

inline struct String String::repeat(unsigned long n) const {
    if (n == 0UL) { return String::new(); }
    struct String out = String::with_cap(self.len * n);
    unsigned long i = 0UL;
    while (i < n) {
        unsafe { out.push((const char*)self.data); }
        i = i + 1UL;
    }
    return out;
}

inline struct String String::capitalize() const {
    if (self.len == 0UL) { return String::new(); }
    struct String out = self.clone();
    unsafe {
        char first = out.data[0];
        if (first >= 'a' && first <= 'z') { out.data[0] = (char)(first - (char)32); }
    }
    // Lowercase the rest
    unsigned long i = 1UL;
    while (i < out.len) {
        unsafe {
            char c = out.data[i];
            if (c >= 'A' && c <= 'Z') { out.data[i] = (char)(c + (char)32); }
        }
        i = i + 1UL;
    }
    return out;
}

// ── Split ─────────────────────────────────────────────────────────────────────
unsigned long String::split(const &char delim, []String out) const {
    unsigned long max = out.len;
    if (max == 0UL || self.cap == 0UL) { return 0UL; }
    unsigned long dlen = str_len(delim);
    if (dlen == 0UL) {
        // Empty delimiter: return clone in first slot
        unsafe {
            []String arr = out;
            arr[0] = self.clone();
        }
        return 1UL;
    }
    unsigned long cnt = 0UL;
    unsigned long start = 0UL;
    while (start <= self.len && cnt < max) {
        // If this is the last slot, take remainder
        if (cnt == max - 1UL) {
            unsafe {
                []String arr = out;
                arr[cnt] = String::from_n_((const char*)((char*)self.data + start), self.len - start);
            }
            cnt = cnt + 1UL;
            break;
        }
        // Search for delimiter
        long long pos = -1LL;
        if (start + dlen <= self.len) {
            unsigned long j = start;
            while (j + dlen <= self.len) {
                unsafe {
                    if (safe_memcmp_raw_((const unsigned char*)((char*)self.data + j), (const unsigned char*)delim, dlen) == 0) {
                        pos = (long long)j;
                        break;
                    }
                }
                j = j + 1UL;
            }
        }
        if (pos < 0LL) {
            // No more delimiters — take the rest
            unsafe {
                []String arr = out;
                arr[cnt] = String::from_n_((const char*)((char*)self.data + start), self.len - start);
            }
            cnt = cnt + 1UL;
            break;
        }
        // Extract segment before delimiter
        unsafe {
            []String arr = out;
            arr[cnt] = String::from_n_((const char*)((char*)self.data + start), (unsigned long)pos - start);
        }
        cnt = cnt + 1UL;
        start = (unsigned long)pos + dlen;
    }
    // If string ends with delimiter and we have room, add empty string
    if (start == self.len && cnt < max && cnt > 0UL) {
        unsafe {
            []String arr = out;
            arr[cnt] = String::new();
        }
        cnt = cnt + 1UL;
    }
    return cnt;
}

unsigned long String::split_lines([]String out) const {
    unsigned long max = out.len;
    if (max == 0UL || self.cap == 0UL) { return 0UL; }
    unsigned long cnt = 0UL;
    unsigned long start = 0UL;
    while (start <= self.len && cnt < max) {
        if (cnt == max - 1UL) {
            unsafe {
                []String arr = out;
                arr[cnt] = String::from_n_((const char*)((char*)self.data + start), self.len - start);
            }
            cnt = cnt + 1UL;
            break;
        }
        // Find \n or \r\n
        unsigned long j = start;
        long long pos = -1LL;
        unsigned long skip = 1UL; // how many bytes the line ending occupies
        while (j < self.len) {
            unsafe {
                if (self.data[j] == '\n') {
                    pos = (long long)j;
                    skip = 1UL;
                    break;
                }
                if (self.data[j] == '\r') {
                    pos = (long long)j;
                    if (j + 1UL < self.len && self.data[j + 1UL] == '\n') {
                        skip = 2UL;
                    } else {
                        skip = 1UL;
                    }
                    break;
                }
            }
            j = j + 1UL;
        }
        if (pos < 0LL) {
            unsafe {
                []String arr = out;
                arr[cnt] = String::from_n_((const char*)((char*)self.data + start), self.len - start);
            }
            cnt = cnt + 1UL;
            break;
        }
        unsafe {
            []String arr = out;
            arr[cnt] = String::from_n_((const char*)((char*)self.data + start), (unsigned long)pos - start);
        }
        cnt = cnt + 1UL;
        start = (unsigned long)pos + skip;
    }
    return cnt;
}

unsigned long String::split_whitespace([]String out) const {
    unsigned long max = out.len;
    if (max == 0UL || self.cap == 0UL) { return 0UL; }
    unsigned long cnt = 0UL;
    unsigned long i = 0UL;
    // Skip leading whitespace
    while (i < self.len) {
        char c;
        unsafe { c = self.data[i]; }
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { i = i + 1UL; }
        else { break; }
    }
    while (i < self.len && cnt < max) {
        // If last slot, take remainder (trimmed of trailing whitespace)
        if (cnt == max - 1UL) {
            unsigned long end = self.len;
            while (end > i) {
                char c;
                unsafe { c = self.data[end - 1UL]; }
                if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { end = end - 1UL; }
                else { break; }
            }
            unsafe {
                []String arr = out;
                arr[cnt] = String::from_n_((const char*)((char*)self.data + i), end - i);
            }
            cnt = cnt + 1UL;
            break;
        }
        // Find end of current token
        unsigned long start = i;
        while (i < self.len) {
            char c;
            unsafe { c = self.data[i]; }
            if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { break; }
            i = i + 1UL;
        }
        unsafe {
            []String arr = out;
            arr[cnt] = String::from_n_((const char*)((char*)self.data + start), i - start);
        }
        cnt = cnt + 1UL;
        // Skip whitespace between tokens
        while (i < self.len) {
            char c;
            unsafe { c = self.data[i]; }
            if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { i = i + 1UL; }
            else { break; }
        }
    }
    return cnt;
}

// ── Comparison ────────────────────────────────────────────────────────────────
inline int String::eq(&String other) const {
    if (self.len != other.len) { return 0; }
    unsafe { return safe_memcmp_raw_((const unsigned char*)self.data, (const unsigned char*)other.data, self.len) == 0; }
}

inline int String::eq_cstr(const &char other) const {
    unsafe { return str_cmp((const char*)self.data, other) == 0; }
}

inline int String::cmp(&String other) const {
    unsafe { return str_cmp((const char*)self.data, (const char*)other.data); }
}

int String::lt(&String other) const { return self.cmp(other) < 0; }
int String::gt(&String other) const { return self.cmp(other) > 0; }

inline int String::eq_ignore_case(&String other) const {
    if (self.len != other.len) { return 0; }
    unsigned long i = 0UL;
    while (i < self.len) {
        unsafe {
            char a = self.data[i];
            char b = other.data[i];
            if (a >= 'A' && a <= 'Z') { a = (char)(a + (char)32); }
            if (b >= 'A' && b <= 'Z') { b = (char)(b + (char)32); }
            if (a != b) { return 0; }
        }
        i = i + 1UL;
    }
    return 1;
}

inline int String::eq_cstr_ignore_case(const &char other) const {
    unsigned long olen = str_len(other);
    if (self.len != olen) { return 0; }
    unsigned long i = 0UL;
    while (i < self.len) {
        unsafe {
            char a = self.data[i];
            char b = other[i];
            if (a >= 'A' && a <= 'Z') { a = (char)(a + (char)32); }
            if (b >= 'A' && b <= 'Z') { b = (char)(b + (char)32); }
            if (a != b) { return 0; }
        }
        i = i + 1UL;
    }
    return 1;
}

// ── Query ─────────────────────────────────────────────────────────────────────
inline int String::is_ascii() const {
    unsigned long i = 0UL;
    while (i < self.len) {
        unsafe { if ((unsigned char)self.data[i] > 127) { return 0; } }
        i = i + 1UL;
    }
    return 1;
}

inline int String::is_numeric() const {
    if (self.len == 0UL) { return 0; }
    unsigned long i = 0UL;
    while (i < self.len) {
        char c;
        unsafe { c = self.data[i]; }
        if (c < '0' || c > '9') { return 0; }
        i = i + 1UL;
    }
    return 1;
}

inline int String::is_alphanumeric() const {
    if (self.len == 0UL) { return 0; }
    unsigned long i = 0UL;
    while (i < self.len) {
        char c;
        unsafe { c = self.data[i]; }
        int ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
        if (!ok) { return 0; }
        i = i + 1UL;
    }
    return 1;
}

// ── Conversion ────────────────────────────────────────────────────────────────
inline long long String::parse_int(?&int ok) const {
    unsafe { return str_to_int((const char*)self.data, ok); }
}

inline double String::parse_float(?&int ok) const {
    unsafe { return str_to_float((const char*)self.data, ok); }
}

} // namespace std