#ifndef _STRVIEW_H #define _STRVIEW_H // NOMENCLATURE: // - Chop: Modifies the original view and returns the chopped part. // - Trim: Operations that chop whitespace from the left, right, or both sides of the view. // - Slice: Returns a new view that is a slice of the original view, without modifying the original view. // // As a general rule of thumb, functions that take the view as a pointer will modify it // while functions that take the view by value will not modify it. #ifndef STRVIEW_DEF #define STRVIEW_DEF static #endif #include #include #include // SAFETY: The user must ensure that the data pointer lives for the lifetime of the strview. typedef struct { const char* data; size_t len; } strview; typedef bool (*sv_predicate)(char); #define svlit(cstrlit) ((strview){ (cstrlit), sizeof((cstrlit)) - 1 }) #define svarg(sv) (int)(sv).len, (sv).data #define svfmt "%.*s" // SAFETY: The caller must ensure that the cstr is null-terminated. STRVIEW_DEF strview sv_from_cstr(const char* cstr); // SAFETY: The caller must ensure that the data pointer is valid for len bytes. STRVIEW_DEF strview sv_from_data(const char* data, size_t len); STRVIEW_DEF strview sv_chop_left(strview* sv, size_t n); STRVIEW_DEF strview sv_chop_right(strview* sv, size_t n); STRVIEW_DEF strview sv_slice(strview sv, size_t left, size_t right); STRVIEW_DEF strview sv_chop_left_while(strview* sv, sv_predicate predicate); STRVIEW_DEF strview sv_chop_right_while(strview* sv, sv_predicate predicate); STRVIEW_DEF strview sv_trim_left(strview* sv); STRVIEW_DEF strview sv_trim_right(strview* sv); STRVIEW_DEF strview sv_trim(strview* sv); // OWNERSHIP: The caller is responsible for freeing the returned string. STRVIEW_DEF char* sv_to_cstr(strview sv); STRVIEW_DEF strview sv_chop_by_char(strview* sv, char delimiter); STRVIEW_DEF strview sv_chop_by_predicate(strview* sv, sv_predicate predicate); STRVIEW_DEF strview sv_chop_by_strview(strview* sv, strview delimiter); STRVIEW_DEF bool sv_starts_with(strview sv, strview prefix); STRVIEW_DEF bool sv_ends_with(strview sv, strview suffix); STRVIEW_DEF size_t sv_find(strview sv, strview substr, bool* found); STRVIEW_DEF size_t sv_rfind(strview sv, strview substr, bool* found); STRVIEW_DEF bool sv_contains(strview sv, strview substr); STRVIEW_DEF size_t sv_find_char(strview sv, char c, bool* found); STRVIEW_DEF size_t sv_rfind_char(strview sv, char c, bool* found); STRVIEW_DEF bool sv_contains_char(strview sv, char c); STRVIEW_DEF int sv_compare(strview sv1, strview sv2); STRVIEW_DEF bool sv_equal(strview sv1, strview sv2); STRVIEW_DEF bool sv_is_all_space(strview sv); STRVIEW_DEF bool sv_empty(strview sv); STRVIEW_DEF char sv_first(strview sv); STRVIEW_DEF char sv_last(strview sv); STRVIEW_DEF bool sv_space_predicate(char c); STRVIEW_DEF bool sv_alpha_predicate(char c); STRVIEW_DEF bool sv_numeric_predicate(char c); STRVIEW_DEF bool sv_alphanumeric_predicate(char c); STRVIEW_DEF bool sv_hex_predicate(char c); STRVIEW_DEF bool sv_identifier_predicate(char c); // Aliases for common naming conventions #define sv_split sv_chop_by_char #define sv_split_char sv_chop_by_char #define sv_split_predicate sv_chop_by_predicate #define sv_split_strview sv_chop_by_strview #define sv_front sv_first #define sv_back sv_last #ifdef STRVIEW_IMPLEMENTATION #include STRVIEW_DEF strview sv_from_data(const char* data, size_t len) { return (strview){ data, len }; } STRVIEW_DEF strview sv_from_cstr(const char* cstr) { size_t len = 0; while (cstr[len] != '\0') { len++; } return sv_from_data(cstr, len); } // If the requested chop length is greater than the view length, the entire view is returned and the original view is emptied. STRVIEW_DEF strview sv_chop_left(strview* sv, size_t n) { if (n > sv->len) { n = sv->len; } strview result = { sv->data, n }; sv->data += n; sv->len -= n; return result; } // If the requested chop length is greater than the view length, the entire view is returned and the original view is emptied. STRVIEW_DEF strview sv_chop_right(strview* sv, size_t n) { if (n > sv->len) { n = sv->len; } strview result = { sv->data + sv->len - n, n }; sv->len -= n; return result; } // The caller must ensure that left + right <= sv.len. // If the requested slice is out of bounds, an empty view is returned. STRVIEW_DEF strview sv_slice(strview sv, size_t left, size_t right) { if (left + right > sv.len) { // If the requested slice is out of bounds, return an empty view. return sv_from_data(sv.data, 0); } return sv_from_data(sv.data + left, sv.len - left - right); } STRVIEW_DEF strview sv_chop_left_while(strview* sv, sv_predicate predicate) { size_t n = 0; while (n < sv->len && predicate(sv->data[n])) { n++; } return sv_chop_left(sv, n); } STRVIEW_DEF strview sv_chop_right_while(strview* sv, sv_predicate predicate) { size_t n = 0; while (n < sv->len && predicate(sv->data[sv->len - 1 - n])) { n++; } return sv_chop_right(sv, n); } STRVIEW_DEF bool sv_space_predicate(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; } STRVIEW_DEF bool sv_alpha_predicate(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } STRVIEW_DEF bool sv_numeric_predicate(char c) { return c >= '0' && c <= '9'; } STRVIEW_DEF bool sv_alphanumeric_predicate(char c) { return sv_alpha_predicate(c) || sv_numeric_predicate(c); } STRVIEW_DEF bool sv_hex_predicate(char c) { return sv_numeric_predicate(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } STRVIEW_DEF bool sv_identifier_predicate(char c) { return sv_alphanumeric_predicate(c) || c == '_'; } STRVIEW_DEF strview sv_trim_left(strview* sv) { return sv_chop_left_while(sv, sv_space_predicate); } STRVIEW_DEF strview sv_trim_right(strview* sv) { return sv_chop_right_while(sv, sv_space_predicate); } STRVIEW_DEF strview sv_trim(strview* sv) { strview left = sv_trim_left(sv); strview right = sv_trim_right(sv); return sv_from_data(left.data, left.len + right.len); } STRVIEW_DEF char* sv_to_cstr(strview sv) { char* cstr = (char*)malloc(sv.len + 1); if (cstr == NULL) { return NULL; } for (size_t i = 0; i < sv.len; i++) { cstr[i] = sv.data[i]; } cstr[sv.len] = '\0'; return cstr; } STRVIEW_DEF strview sv_chop_by_char(strview* sv, char delimiter) { size_t i = 0; while (i < sv->len && sv->data[i] != delimiter) { i++; } strview result = sv_from_data(sv->data, i); if (i < sv->len) { sv->data += i + 1; // Skip the delimiter sv->len -= i + 1; } else { sv->data += i; sv->len -= i; } return result; } STRVIEW_DEF strview sv_chop_by_predicate(strview* sv, sv_predicate predicate) { size_t i = 0; while (i < sv->len && !predicate(sv->data[i])) { i++; } strview result = sv_from_data(sv->data, i); if (i < sv->len) { sv->data += i + 1; // Skip the delimiter sv->len -= i + 1; } else { sv->data += i; sv->len -= i; } return result; } STRVIEW_DEF strview sv_chop_by_strview(strview* sv, strview delimiter) { size_t i = 0; while (i + delimiter.len <= sv->len) { if (sv->data[i] == delimiter.data[0] && strncmp(sv->data + i, delimiter.data, delimiter.len) == 0) { break; } i++; } strview result = sv_from_data(sv->data, i); if (i + delimiter.len <= sv->len) { sv->data += i + delimiter.len; // Skip the delimiter sv->len -= i + delimiter.len; } else { sv->data += i; sv->len -= i; } return result; } STRVIEW_DEF bool sv_starts_with(strview sv, strview prefix) { if (prefix.len > sv.len) { return false; } return strncmp(sv.data, prefix.data, prefix.len) == 0; } STRVIEW_DEF bool sv_ends_with(strview sv, strview suffix) { if (suffix.len > sv.len) { return false; } return strncmp(sv.data + sv.len - suffix.len, suffix.data, suffix.len) == 0; } STRVIEW_DEF size_t sv_find(strview sv, strview substr, bool* found) { if (substr.len > sv.len) { *found = false; return 0; } for (size_t i = 0; i <= sv.len - substr.len; i++) { if (strncmp(sv.data + i, substr.data, substr.len) == 0) { *found = true; return i; } } *found = false; return 0; } STRVIEW_DEF size_t sv_rfind(strview sv, strview substr, bool* found) { if (substr.len > sv.len) { *found = false; return 0; } for (size_t i = sv.len - substr.len + 1; i-- > 0;) { if (strncmp(sv.data + i, substr.data, substr.len) == 0) { *found = true; return i; } } *found = false; return 0; } STRVIEW_DEF bool sv_contains(strview sv, strview substr) { bool found; sv_find(sv, substr, &found); return found; } STRVIEW_DEF size_t sv_find_char(strview sv, char c, bool* found) { for (size_t i = 0; i < sv.len; i++) { if (sv.data[i] == c) { *found = true; return i; } } *found = false; return 0; } STRVIEW_DEF size_t sv_rfind_char(strview sv, char c, bool* found) { for (size_t i = sv.len; i-- > 0;) { if (sv.data[i] == c) { *found = true; return i; } } *found = false; return 0; } STRVIEW_DEF bool sv_contains_char(strview sv, char c) { bool found; sv_find_char(sv, c, &found); return found; } STRVIEW_DEF int sv_compare(strview sv1, strview sv2) { size_t min_len = sv1.len < sv2.len ? sv1.len : sv2.len; int cmp = strncmp(sv1.data, sv2.data, min_len); if (cmp != 0) { return cmp; } if (sv1.len < sv2.len) { return -1; } else if (sv1.len > sv2.len) { return 1; } else { return 0; } } STRVIEW_DEF bool sv_equal(strview sv1, strview sv2) { return sv_compare(sv1, sv2) == 0; } STRVIEW_DEF bool sv_is_all_space(strview sv) { for (size_t i = 0; i < sv.len; i++) { if (!sv_space_predicate(sv.data[i])) { return false; } } return true; } STRVIEW_DEF bool sv_empty(strview sv) { return sv.len == 0; } STRVIEW_DEF char sv_first(strview sv) { if (sv.len == 0) { return '\0'; // Return null character for empty view } return sv.data[0]; } STRVIEW_DEF char sv_last(strview sv) { if (sv.len == 0) { return '\0'; // Return null character for empty view } return sv.data[sv.len - 1]; } #endif // STRVIEW_IMPLEMENTATION #endif // _STRVIEW_H