diff --git a/README.md b/README.md index c6625ad..3e8fd89 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,26 @@ gcc main.c -o app -I/usr/local/include/strlib -L/usr/local/lib -lstrlib ## Reference +### SVArray + +```c +typedef struct { + SV *data; + size_t len; + size_t cap; +} SVArray; +``` + +A heap-allocated growable array of SVs. Returned by `sv_split`. Always call `sva_free` when done. + +`SVArray sva_new(void)` — Creates an empty array. Does not allocate until first push. + +`void sva_push(SVArray *arr, SV sv)` — Appends an SV to the array. Grows automatically. + +`SV sva_get(SVArray *arr, size_t i)` — Returns the SV at index i. Aborts with an error message if i is out of bounds. + +`void sva_free(SVArray *arr)` — Frees the backing array and zeroes the struct. Safe to call on an already-freed `SVArray`. Does not free the individual SVs — they are views and do not own memory. + ### SV **Functions:** @@ -221,12 +241,64 @@ gcc main.c -o app -I/usr/local/include/strlib -L/usr/local/lib -lstrlib `SV sv_chop_by_delim(SV *sv, char delim)` — Returns everything before the first `delim` and advances `sv` past it. If not found, returns the full view and leaves `sv` empty. +`bool sv_is_empty(SV sv)` — Returns true if sv has zero length. + +`bool sv_is_whitespace(SV sv)` — Returns true if every byte is whitespace. Returns false on empty. + +`bool sv_is_alpha(SV sv)` — Returns true if every byte is an alphabetic character. Returns false on empty. + +`bool sv_is_numeric(SV sv)` — Returns true if every byte is a decimal digit. Returns false on empty. + +`bool sv_is_alphanumeric(SV sv)` — Returns true if every byte is alphabetic or a decimal digit. Returns false on empty. + +`bool sv_is_upper(SV sv)` — Returns true if every byte is an uppercase letter. Returns false on empty. + +`bool sv_is_lower(SV sv)` — Returns true if every byte is a lowercase letter. Returns false on empty. + +`size_t sv_count_sv(SV sv, SV needle, bool overlapping)` — Counts occurrences of needle in sv. If overlapping is false, matches do not overlap — `sv_count("aaa", "aa", false)` returns 1. If overlapping is true, every position is checked — `sv_count("aaa", "aa", true)` returns 2. Returns 0 if needle is empty or longer than sv. + +`bool sv_parse_int(SV sv, int *out)` — Parses sv as a decimal integer into `*out`. Returns true on success, false if the input is not a valid integer or overflows the type. If out is NULL, validates without storing. All sv_parse_* variants follow this same contract. + +`bool sv_parse_long(SV sv, long *out)` — Parses as `long`. + +`bool sv_parse_longlong(SV sv, long long *out)` — Parses as `long long`. + +`bool sv_parse_int8(SV sv, int8_t *out)` — Parses as `int8_t`. + +`bool sv_parse_int16(SV sv, int16_t *out)` — Parses as `int16_t`. + +`bool sv_parse_int32(SV sv, int32_t *out)` — Parses as `int32_t`. + +`bool sv_parse_int64(SV sv, int64_t *out)` — Parses as `int64_t`. + +`bool sv_parse_uint8(SV sv, uint8_t *out)` — Parses as `uint8_t`. + +`bool sv_parse_uint16(SV sv, uint16_t *out)` — Parses as `uint16_t`. + +`bool sv_parse_uint32(SV sv, uint32_t *out)` — Parses as `uint32_t`. + +`bool sv_parse_uint64(SV sv, uint64_t *out)` — Parses as `uint64_t`. + +`bool sv_parse_float(SV sv, float *out)` — Parses as `float`. + +`bool sv_parse_double(SV sv, double *out)` — Parses as `double`. + +`SVArray sv_split_char(SV sv, char delim, size_t maxsplit)` — Splits sv on every occurrence of delim. If maxsplit is non-zero, stops after that many splits and puts the remainder in the final element. Empty segments are preserved. Caller must call `sva_free` on the result. + +`SVArray sv_split_sv(SV sv, SV delim, size_t maxsplit)` — Same as `sv_split_char` but splits on a multi-character delimiter. If delim is empty, returns sv unsplit as a single element. + **Macros:** `NEW_SV(s)` — Converts `SV`, `char *`, or `const char *` into an `SV` at compile time via `_Generic`. No copy. `sv_eq(a, b)`, `sv_cmp(a, b)`, `sv_starts_with(a, b)`, `sv_ends_with(a, b)`, `sv_contains(a, b)`, `sv_find(a, b)` — Type-coercing wrappers around their `_sv` counterparts. Accept any mix of `SV`, `char *`, or `const char *` as arguments. +`sv_count(a, b)` — Counts non-overlapping occurrences of b in a. Accepts any mix of SV, `char *`, or `const char *`. + +`sv_count_overlapping(a, b)` — Counts overlapping occurrences of b in a. Accepts any mix of SV, `char *`, or `const char *`. + +`sv_split(sv, delim, maxsplit)` — Splits sv on delim, dispatching to `sv_split_char` or `sv_split_sv` based on the type of delim at compile time. Accepts char, SV, `char *`, or `const char *` as delimiter. Pass 0 for maxsplit to split everything. + `SV_FMT`, `SV_ARGS(sv)` — Use with printf-style functions to print an SV without null-termination: ```c printf(SV_FMT "\n", SV_ARGS(my_sv)); @@ -354,6 +426,16 @@ str_free(&a); str_free(&b); ``` +### `SVArray` views into freed memory + +The SVs inside an `SVArray` returned by `sv_split` are views into the original string's memory. Freeing or modifying the source string while still using the array will leave all the views dangling: +```c +SVArray parts = sv_split(sv_from_cstr(get_temp_string()), ',', 0); // imagine get_temp_string() is a fn that returns temp string +// if get_temp_string()'s memory is gone, parts.data[i] are all dangling +sva_free(&parts); +``` +Make sure the source string outlives the `SVArray`. + ### `SB_AUTO` / `STR_AUTO` are no-ops on unknown compilers On compilers without `__attribute__((cleanup))`, both macros expand to nothing and no warning is emitted at the point of use — only at the point `compat.h` is processed. Variables marked `SB_AUTO` or `STR_AUTO` will not be freed automatically. You will leak memory silently. diff --git a/include/compat.h b/include/compat.h index 9f0ebdc..acc4a67 100644 --- a/include/compat.h +++ b/include/compat.h @@ -42,7 +42,9 @@ /* ── Feature flags ── */ #if defined(STRLIB_COMPILER_GCC) || defined(STRLIB_COMPILER_CLANG) #define STRLIB_HAS_CLEANUP -#define STRLIB_HAS_TYPEOF +#define STRLIB_TYPEOF(x) __typeof__(x) +#else +#define STRLIB_TYPEOF(x) #endif /* _Generic is C11, already guaranteed by standard check above */ diff --git a/include/strlib.h b/include/strlib.h index c585e77..36d0a86 100644 --- a/include/strlib.h +++ b/include/strlib.h @@ -25,6 +25,6 @@ #include "sb.h" #endif -#define STRLIB_VERSION "0.1.0" +#define STRLIB_VERSION "0.2.0-dev" #endif /* STRLIB_H_ */ \ No newline at end of file diff --git a/include/sv.h b/include/sv.h index 31f9815..695b651 100644 --- a/include/sv.h +++ b/include/sv.h @@ -5,6 +5,7 @@ #include #include +#include typedef struct { @@ -12,6 +13,18 @@ typedef struct size_t len; } SV; +typedef struct +{ + SV *data; + size_t len; + size_t cap; +} SVArray; + +SVArray sva_new(void); +void sva_push(SVArray *arr, SV sv); +SV sva_get(SVArray *arr, size_t i); +void sva_free(SVArray *arr); + #define NEW_SV(s) _Generic((s), \ SV: (s), \ char *: sv_from_cstr(s), \ @@ -35,6 +48,31 @@ SV sv_trim(SV sv); SV sv_chop_by_delim(SV *sv, char delim); +bool sv_is_empty(SV sv); +bool sv_is_whitespace(SV sv); +bool sv_is_alpha(SV sv); +bool sv_is_numeric(SV sv); +bool sv_is_alphanumeric(SV sv); +bool sv_is_upper(SV sv); +bool sv_is_lower(SV sv); + +size_t sv_count_sv(SV sv, SV needle, bool overlapping); + +bool sv_parse_int(SV sv, int *out); +bool sv_parse_long(SV sv, long *out); +bool sv_parse_longlong(SV sv, long long *out); +bool sv_parse_int8(SV sv, int8_t *out); +bool sv_parse_int16(SV sv, int16_t *out); +bool sv_parse_int32(SV sv, int32_t *out); +bool sv_parse_int64(SV sv, int64_t *out); +bool sv_parse_uint8(SV sv, uint8_t *out); +bool sv_parse_uint16(SV sv, uint16_t *out); +bool sv_parse_uint32(SV sv, uint32_t *out); +bool sv_parse_uint64(SV sv, uint64_t *out); + +SVArray sv_split_char(SV sv, char delim, size_t maxsplit); +SVArray sv_split_sv(SV sv, SV delim, size_t maxsplit); + #define sv_eq(a, b) sv_eq_sv(NEW_SV(a), NEW_SV(b)) #define sv_cmp(a, b) sv_cmp_sv(NEW_SV(a), NEW_SV(b)) @@ -43,6 +81,15 @@ SV sv_chop_by_delim(SV *sv, char delim); #define sv_contains(a, b) sv_contains_sv(NEW_SV(a), NEW_SV(b)) #define sv_find(a, b) sv_find_sv(NEW_SV(a), NEW_SV(b)) +#define sv_count(a, b) sv_count_sv(NEW_SV(a), NEW_SV(b), false) +#define sv_count_overlapping(a, b) sv_count_sv(NEW_SV(a), NEW_SV(b), true) + +#define sv_split(sv, delim, maxsplit) _Generic((delim), \ + char: sv_split_char((sv), (delim), (maxsplit)), \ + SV: sv_split_sv((sv), (delim), (maxsplit)), \ + char *: sv_split_sv((sv), NEW_SV(delim), (maxsplit)), \ + const char *: sv_split_sv((sv), NEW_SV(delim), (maxsplit))) + #define SV_FMT "%.*s" #define SV_ARGS(sv) (int)(sv).len, (sv).data diff --git a/src/sv.c b/src/sv.c index a6327bf..d009598 100644 --- a/src/sv.c +++ b/src/sv.c @@ -2,6 +2,56 @@ #include #include +#include +#include +#include +#include +#include + +#define SVA_INIT_CAP 8 + +SVArray sva_new(void) +{ + return (SVArray){.data = NULL, .len = 0, .cap = 0}; +} + +void sva_push(SVArray *arr, SV sv) +{ + if (arr->len >= arr->cap) + { + size_t new_cap = arr->cap ? arr->cap * 2 : SVA_INIT_CAP; + arr->data = realloc(arr->data, new_cap * sizeof(SV)); + if (!arr->data) + { + fputs("strlib::sv: out of memory\n", stderr); + abort(); + } + arr->cap = new_cap; + } + arr->data[arr->len++] = sv; +} + +SV sva_get(SVArray *arr, size_t i) +{ + if (i >= arr->len) + { + fprintf(stderr, "strlib::sv: index %zu out of bounds (len %zu)\n", i, arr->len); + abort(); + } + return arr->data[i]; +} + +void sva_free(SVArray *arr) +{ + if (!arr) + return; + free(arr->data); + arr->data = NULL; + arr->len = 0; + arr->cap = 0; +} + +// ---- SV ------------> SV sv_from_cstr(const char *cstr) { @@ -119,4 +169,269 @@ SV sv_chop_by_delim(SV *sv, char delim) } return chopped; +} + +bool sv_is_empty(SV sv) +{ + return sv.len == 0; +} + +bool sv_is_whitespace(SV sv) +{ + for (size_t i = 0; i < sv.len; i++) + if (!isspace((unsigned char)sv.data[i])) + return false; + return sv.len > 0; +} + +bool sv_is_alpha(SV sv) +{ + if (sv.len == 0) + return false; + for (size_t i = 0; i < sv.len; i++) + if (!isalpha((unsigned char)sv.data[i])) + return false; + return true; +} + +bool sv_is_numeric(SV sv) +{ + if (sv.len == 0) + return false; + for (size_t i = 0; i < sv.len; i++) + if (!isdigit((unsigned char)sv.data[i])) + return false; + return true; +} + +bool sv_is_alphanumeric(SV sv) +{ + if (sv.len == 0) + return false; + for (size_t i = 0; i < sv.len; i++) + if (!isalnum((unsigned char)sv.data[i])) + return false; + return true; +} + +bool sv_is_upper(SV sv) +{ + if (sv.len == 0) + return false; + for (size_t i = 0; i < sv.len; i++) + if (!isupper((unsigned char)sv.data[i])) + return false; + return true; +} + +bool sv_is_lower(SV sv) +{ + if (sv.len == 0) + return false; + for (size_t i = 0; i < sv.len; i++) + if (!islower((unsigned char)sv.data[i])) + return false; + return true; +} + +size_t sv_count_sv(SV sv, SV needle, bool overlapping) +{ + if (needle.len == 0 || needle.len > sv.len) + return 0; + + size_t count = 0; + size_t i = 0; + size_t step = overlapping ? 1 : needle.len; + + while (i <= sv.len - needle.len) + { + if (memcmp(sv.data + i, needle.data, needle.len) == 0) + { + count++; + i += step; + } + else + i++; + } + + return count; +} + +static bool parse_longlong(SV sv, long long *out) +{ + if (sv.len == 0 || sv.len >= 32) + return false; + + char buf[32]; + memcpy(buf, sv.data, sv.len); + buf[sv.len] = '\0'; + + char *end; + errno = 0; + long long val = strtoll(buf, &end, 10); + + if (end != buf + sv.len || errno != 0) + return false; + + if (out) + *out = val; + return true; +} + +static bool parse_ulonglong(SV sv, unsigned long long *out) +{ + if (sv.len == 0 || sv.len >= 32) + return false; + + char buf[32]; + memcpy(buf, sv.data, sv.len); + buf[sv.len] = '\0'; + + char *end; + errno = 0; + unsigned long long val = strtoull(buf, &end, 10); + + if (end != buf + sv.len || errno != 0) + return false; + + if (out) + *out = val; + return true; +} + +static bool parse_double(SV sv, double *out) +{ + if (sv.len == 0 || sv.len >= 64) + return false; + + char buf[64]; + memcpy(buf, sv.data, sv.len); + buf[sv.len] = '\0'; + + char *end; + errno = 0; + double val = strtod(buf, &end); + + if (end != buf + sv.len || errno != 0) + return false; + + if (out) + *out = val; + return true; +} + +#define PARSE_SIGNED(sv, out, min, max) \ + long long val; \ + if (!parse_longlong(sv, &val)) \ + return false; \ + if (val < (min) || val > (max)) \ + return false; \ + if (out) \ + *(out) = (STRLIB_TYPEOF(*(out)))val; \ + return true; + +#define PARSE_UNSIGNED(sv, out, max) \ + unsigned long long val; \ + if (!parse_ulonglong(sv, &val)) \ + return false; \ + if (val > (max)) \ + return false; \ + if (out) \ + *(out) = (STRLIB_TYPEOF(*(out)))val; \ + return true; + +bool sv_parse_int(SV sv, int *out) { PARSE_SIGNED(sv, out, INT_MIN, INT_MAX) } +bool sv_parse_long(SV sv, long *out) { PARSE_SIGNED(sv, out, LONG_MIN, LONG_MAX) } +bool sv_parse_longlong(SV sv, long long *out) { PARSE_SIGNED(sv, out, LLONG_MIN, LLONG_MAX) } +bool sv_parse_int8(SV sv, int8_t *out) { PARSE_SIGNED(sv, out, INT8_MIN, INT8_MAX) } +bool sv_parse_int16(SV sv, int16_t *out) { PARSE_SIGNED(sv, out, INT16_MIN, INT16_MAX) } +bool sv_parse_int32(SV sv, int32_t *out) { PARSE_SIGNED(sv, out, INT32_MIN, INT32_MAX) } +bool sv_parse_int64(SV sv, int64_t *out) { PARSE_SIGNED(sv, out, INT64_MIN, INT64_MAX) } + +bool sv_parse_uint8(SV sv, uint8_t *out) { PARSE_UNSIGNED(sv, out, UINT8_MAX) } +bool sv_parse_uint16(SV sv, uint16_t *out) { PARSE_UNSIGNED(sv, out, UINT16_MAX) } +bool sv_parse_uint32(SV sv, uint32_t *out) { PARSE_UNSIGNED(sv, out, UINT32_MAX) } +bool sv_parse_uint64(SV sv, uint64_t *out) { PARSE_UNSIGNED(sv, out, UINT64_MAX) } + +bool sv_parse_float(SV sv, float *out) +{ + double val; + if (!parse_double(sv, &val)) + return false; + if (val < -FLT_MAX || val > FLT_MAX) + return false; + if (out) + *out = (float)val; + return true; +} + +bool sv_parse_double(SV sv, double *out) { return parse_double(sv, out); } + +SVArray sv_split_char(SV sv, char delim, size_t maxsplit) +{ + SVArray arr = sva_new(); + size_t splits = 0; + + while (sv.len > 0) + { + if (maxsplit != 0 && splits >= maxsplit) + { + sva_push(&arr, sv); + return arr; + } + + size_t i = 0; + while (i < sv.len && sv.data[i] != delim) + i++; + + sva_push(&arr, sv_from_parts(sv.data, i)); + splits++; + + if (i < sv.len) + { + sv.data += i + 1; + sv.len -= i + 1; + } + else + break; + } + + return arr; +} + +SVArray sv_split_sv(SV sv, SV delim, size_t maxsplit) +{ + SVArray arr = sva_new(); + + if (delim.len == 0) + { + sva_push(&arr, sv); + return arr; + } + + size_t splits = 0; + + while (sv.len > 0) + { + if (maxsplit != 0 && splits >= maxsplit) + { + sva_push(&arr, sv); + return arr; + } + + size_t i = sv_find_sv(sv, delim); + + sva_push(&arr, sv_from_parts(sv.data, i)); + splits++; + + if (i < sv.len) + { + sv.data += i + delim.len; + sv.len -= i + delim.len; + } + else + break; + } + + return arr; } \ No newline at end of file