diff --git a/README.txt b/README.txt index 9c4394d..6b19e41 100644 --- a/README.txt +++ b/README.txt @@ -22,3 +22,26 @@ int main() { Define DEBUG to initalize memory with OXFE, to detect uninitialized memory. Define DEBUG AND TK_MEMORY_TRACK to catch memory leaks when calling tk_check_memory_leaks(). + + +*** tk_strings.h *** +``` +#define TK_STRINGS_IMPLEMENTATION +#include "tk_strings.h" + +#include + +int main() { + char str[] = " Hello, World "; + tk_str_to_lower(str); + + char* trimmed = tk_str_trim(str); + uint8_t contains = tk_str_contains(trimmed, "hello"); + + free(trimmed); + return contains ? 0 : 1; +} + +``` + +String utilities include counting characters/substrings, splitting by character or substring, case conversion, concatenation, trimming, and substring checks. diff --git a/tk_strings.h b/tk_strings.h new file mode 100644 index 0000000..f227c9f --- /dev/null +++ b/tk_strings.h @@ -0,0 +1,280 @@ +#ifndef TK_STRINGS_H +#define TK_STRINGS_H + +/** + * @file tk_strings.h + * @brief Single-header string utilities. + * + * Define TK_STRINGS_IMPLEMENTATION in exactly one translation unit before + * including this file to include the implementation. + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Count occurrences of a character in a C string. + * @param str Null-terminated input string. Must not be NULL. + * @param c Character to count. + * @return Number of times c appears in str. + */ +size_t tk_str_count_occurrences(const char* str, char c); + +/** + * @brief Count occurrences of a substring in a C string. + * @param str Null-terminated input string. Must not be NULL. + * @param substr Null-terminated substring to count. Must not be NULL or empty. + * @return Number of times substr appears in str. + */ +size_t tk_str_count_occurrences_of_substring(const char* str, const char* substr); + +/** + * @brief Split a string by a delimiter character. + * + * This copies all parts. The caller is responsible for freeing each string in + * result and the result array itself. + * + * @param str Null-terminated input string. Must not be NULL. + * @param delimiter Delimiter character. + * @param[out] result On success, set to a malloc'd array of char* parts. + * @return Number of parts. An empty string returns one empty part. If the + * result array allocation fails, returns 0. If a part allocation fails, returns + * the number of parts allocated so far and result points to that partial array. + */ +size_t tk_str_split_by_char(const char* str, char delimiter, char*** result); + +/** + * @brief Split a string by a delimiter substring. + * + * This copies all parts. The caller is responsible for freeing each string in + * result and the result array itself. + * + * @param str Null-terminated input string. Must not be NULL. + * @param substr Null-terminated delimiter substring. Must not be NULL or empty. + * @param[out] result On success, set to a malloc'd array of char* parts. + * @return Number of parts. If the result array allocation fails, returns 0. If + * a part allocation fails, returns the number of parts allocated so far and + * result points to that partial array. + */ +size_t tk_str_split_by_substring(const char* str, const char* substr, char*** result); + +/** + * @brief Convert all lowercase ASCII letters in a string to uppercase. + * @param str Null-terminated string to modify in place. Must not be NULL. + */ +void tk_str_to_upper(char* str); + +/** + * @brief Convert all uppercase ASCII letters in a string to lowercase. + * @param str Null-terminated string to modify in place. Must not be NULL. + */ +void tk_str_to_lower(char* str); + +/** + * @brief Concatenate two null-terminated strings into a newly allocated string. + * @param str1 First input string. Must not be NULL. + * @param str2 Second input string. Must not be NULL. + * @return Newly allocated concatenated string, or NULL on allocation error. + */ +char* tk_str_concat(const char* str1, const char* str2); + +/** + * @brief Trim leading and trailing whitespace from a string. + * @param str Null-terminated input string. Must not be NULL, empty, or all whitespace. + * @return Newly allocated trimmed string, or NULL on allocation error. + */ +char* tk_str_trim(const char* str); + +/** + * @brief Check whether a string contains a substring. + * @param str Null-terminated input string. Must not be NULL. + * @param substr Null-terminated substring to search for. Must not be NULL. + * @return 1 if str contains substr, 0 otherwise. + */ +uint8_t tk_str_contains(const char* str, const char* substr); + +#ifdef __cplusplus +} +#endif + +#endif /* TK_STRINGS_H */ + +#ifdef TK_STRINGS_IMPLEMENTATION + +#include +#include +#include + +inline size_t tk_str_count_occurrences(const char* str, const char c) { + size_t i = 0; + size_t count = 0; + while (str[i] != '\0') { + if (str[i] == c) { + count++; + } + i++; + } + + return count; +} + +inline size_t tk_str_count_occurrences_of_substring(const char* str, const char* substr) { + size_t i = 0; + size_t count = 0; + while (str[i] != '\0') { + if (strstr(&str[i], substr) == &str[i]) { + count++; + } + i++; + } + return count; +} + +inline size_t tk_str_split_by_char(const char* str, const char delimiter, char*** result) { + const size_t size = tk_str_count_occurrences(str, delimiter); + char** parts = malloc((size + 1) * sizeof(char*)); + if (parts == NULL) { + *result = NULL; + return 0; + } + + size_t curr_part_idx = 0; + size_t last_part_end = 0; + size_t idx = 0; + while (str[idx] != '\0') { + if (str[idx] == delimiter) { + const size_t len = idx - last_part_end; + char* part = malloc(sizeof(char) * (len + 1)); + if (part == NULL) { + *result = parts; + return curr_part_idx; + } + memcpy(part, &str[last_part_end], len); + part[idx - last_part_end] = '\0'; + + parts[curr_part_idx++] = part; + last_part_end = idx; + last_part_end++; + } + idx++; + } + char* part = malloc(sizeof(char) * (idx - last_part_end + 1)); + if (part == NULL) { + *result = parts; + return curr_part_idx; + } + memcpy(part, &str[last_part_end], idx - last_part_end); + part[idx - last_part_end] = '\0'; + parts[curr_part_idx++] = part; + + *result = parts; + return curr_part_idx; +} + +inline size_t tk_str_split_by_substring(const char* str, const char* substr, char*** result) { + const size_t size = tk_str_count_occurrences_of_substring(str, substr); + char** parts = malloc((size + 1) * sizeof(char*)); + if (parts == NULL) { + *result = NULL; + return 0; + } + + size_t curr_part_idx = 0; + size_t last_part_end = 0; + size_t idx = 0; + + while (str[idx] != '\0') { + if (strstr(&str[idx], substr) == &str[idx]) { + const size_t len = idx - last_part_end; + char* part = malloc(sizeof(char) * (len + 1)); + if (part == NULL) { + *result = parts; + return curr_part_idx; + } + memcpy(part, &str[last_part_end], len); + part[len] = '\0'; + + parts[curr_part_idx++] = part; + last_part_end = idx; + last_part_end += strlen(substr); + } + idx++; + } + const size_t len = idx - last_part_end; + char* part = malloc(sizeof(char) * (len + 1)); + if (part == NULL) { + *result = parts; + return curr_part_idx; + } + memcpy(part, &str[last_part_end], len); + part[len] = '\0'; + + parts[curr_part_idx++] = part; + + *result = parts; + return curr_part_idx; +} + +inline void tk_str_to_upper(char* str) { + for (size_t i = 0; str[i] != '\0'; i++) { + if (str[i] >= 'a' && str[i] <= 'z') { + str[i] -= 'a' - 'A'; + } + } +} + +inline void tk_str_to_lower(char* str) { + for (size_t i = 0; str[i] != '\0'; i++) { + if (str[i] >= 'A' && str[i] <= 'Z') { + str[i] += 'a' - 'A'; + } + } +} + +inline char* tk_str_concat(const char* str1, const char* str2) { + const size_t len1 = strlen(str1); + const size_t len2 = strlen(str2); + + char* result = malloc(sizeof(char) * (len1 + len2 + 1)); + if (result == NULL) { + return NULL; + } + memcpy(result, str1, len1); + memcpy(&result[len1], str2, len2); + result[len1 + len2] = '\0'; + return result; +} + +static inline bool tk_internal_str_char_is_rubbish(const char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; +} + +inline char* tk_str_trim(const char* str) { + size_t start = 0; + size_t end = strlen(str) - 1; + while (tk_internal_str_char_is_rubbish(str[start])) { + start++; + } + while (tk_internal_str_char_is_rubbish(str[end])) { + end--; + } + + char* new_str = malloc(sizeof(char) * (end - start + 2)); + if (new_str == NULL) { + return NULL; + } + memcpy(new_str, &str[start], end - start + 1); + new_str[end - start + 1] = '\0'; + + return new_str; +} + +inline uint8_t tk_str_contains(const char* str, const char* substr) { + return strstr(str, substr) != NULL; +} + +#endif /* TK_STRINGS_IMPLEMENTATION */