From a2a91bbce92537ecda707b777c9632e810165099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20R=C3=B6ger?= Date: Sun, 12 Apr 2026 16:54:06 +0200 Subject: [PATCH] feat(sv): add string view lib --- CMakeLists.txt | 6 +- app/strings.c | 49 +++++ include/c-libs/string-view.h | 350 +++++++++++++++++++++++++++++++++++ src/string-view.c | 2 + 4 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 app/strings.c create mode 100644 include/c-libs/string-view.h create mode 100644 src/string-view.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d186be..2b9f648 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,11 +5,15 @@ project( DESCRIPTION "C Libraries for recreational Programming." LANGUAGES C) -add_library(c-libs ${CMAKE_CURRENT_SOURCE_DIR}/src/dyn-arr.c) +add_library(c-libs ${CMAKE_CURRENT_SOURCE_DIR}/src/dyn-arr.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/string-view.c) target_include_directories( c-libs PUBLIC $ $) +add_executable(strings ${CMAKE_CURRENT_SOURCE_DIR}/app/strings.c) +target_link_libraries(strings PRIVATE c-libs) + include(CTest) if(BUILD_TESTING) find_package(PkgConfig REQUIRED) diff --git a/app/strings.c b/app/strings.c new file mode 100644 index 0000000..875991c --- /dev/null +++ b/app/strings.c @@ -0,0 +1,49 @@ +#include "c-libs/dyn-arr.h" +#include "c-libs/string-view.h" +#include + +int main() { + StringView sv = sv_new("Hello World\n"); + + sv_puts(sv); + sv_puts(sv_drop(sv_shrink(sv, 3), 2)); + sv_puts(sv_drop(sv_shrink(sv, 5), 9)); + + StringView sv2 = sv_new("AAA Test BBB"); + sv_puts(sv2); + puts(""); + sv2 = sv_seek(sv2, ' '); + sv_puts(sv2); + puts(""); + sv2 = sv_seek_back(sv2, ' '); + sv_puts(sv2); + puts(""); + + StringView csv = sv_new(" 100 , 2000, 10 ,1,871"); + + StringView *values = NULL; + darr_init(values, 0); + + while (sv_len(csv)) { + StringView item = sv_split_at(&csv, ','); + darr_push(values, sv_trim(item, isspace)); + } + + char *all = sv_concat_with_sep(values, darr_size(values), sv_new("::")); + puts(all); + + darr_clear(values); + + StringView ccsv = sv_new(all); + while (sv_len(ccsv)) { + StringView item = sv_split_at_sv(&ccsv, sv_new("::")); + darr_push(values, sv_trim(item, isspace)); + } + + char *all2 = sv_concat_with_sep(values, darr_size(values), sv_new("-+-")); + puts(all2); + + free(all); + free(all2); + darr_free(values); +} diff --git a/include/c-libs/string-view.h b/include/c-libs/string-view.h new file mode 100644 index 0000000..9a4e8be --- /dev/null +++ b/include/c-libs/string-view.h @@ -0,0 +1,350 @@ +#ifndef CLIBS_STRING_VIEW_H +#define CLIBS_STRING_VIEW_H + +#include +#include +#include +#include + +/// A string view consisting of a start pointer _data_ +/// and an end pointer _end_. The end points behind the last character. +typedef struct { + const char *data; + const char *end; +} StringView; + +/// +///@brief Get the length of a string view +/// +///@param StringView sv the string view +/// +///@return size_t it's length (data - end) +/// +size_t sv_len(StringView sv); + +/// +///@brief Construct a new string view with a data and length specification +/// +///@param const char *str the string to view (can be non-null-terminated) +///@param size_t len the length of the string +/// +///@return StringView the new string view +/// +StringView sv_new_sized(const char *str, size_t len); + +/// +///@brief Construct a new string view from a null-terminated string +/// +///@param const char *str the null-terminated string +/// +///@return StringView a new string view {.data = str, .end = str + strlen(str)} +/// +StringView sv_new(const char *str); + +/// +///@brief Drop the first n chars of a string view (non-destructive) +/// +///@param StringView sv the original sv +///@param size_t n the number of chars to drop from the beginning +/// +///@return StringView the resulting sv +/// +StringView sv_drop(StringView sv, size_t n); + +/// +///@brief Get the first n chars of a string view (non-destructive) +/// +///@param StringView sv the original sv +///@param size_t n the number of chars to take +/// +///@return StringView the resulting sv +/// +StringView sv_take(StringView sv, size_t n); + +/// +///@brief Remove the n last chars of a sv (non-destructive) +/// +///@param StringView sv the original sv +///@param size_t n the number of chars to remove from the end +/// +///@return StringView the resulting sv +/// +StringView sv_shrink(StringView sv, size_t n); + +/// +///@brief call _putc_ for each char in the string view +/// +///@param StringView sv the string view to print +/// +void sv_puts(StringView sv); + +/// +///@brief Remove chars from the front until a specified char is encountered +///(non-destructive) +/// +///@param StringView sv the original sv +///@param char c the char to find +/// +///@return StringView the resulting string view (may be empty) +/// +StringView sv_seek(StringView sv, char c); + +/// +///@brief Like sv_seek but seeking from end to begin +/// +///@param StringView sv the original sv +///@param char c the char to find +/// +///@return StringView the resulting string view +/// +StringView sv_seek_back(StringView sv, char c); + +/// +///@brief Remove chars from the front matching pred (non-destructive) +/// +///@param StringView sv the original sv +///@param int (*pred)(int) the predicate (returns non-zero if char shall be +/// removed) +/// +///@return StringView the resulting string view +/// +StringView sv_trim_front(StringView sv, int (*pred)(int)); + +/// +///@brief Like sv_trim_front but from end to begin +/// +///@param StringView sv the original sv +///@param int (*pred)(int) the predicate (returns non-zero if char shall be +/// +///@return StringView the resulting string view +/// +StringView sv_trim_back(StringView sv, int (*pred)(int)); + +/// +///@brief Effectively sv_trim_front and sv_trim_back +/// +///@param StringView sv the original sv +///@param int (*pred)(int) the predicate (returns non-zero if char shall be +/// +///@return StringView the resulting string view +/// +StringView sv_trim(StringView sv, int (*pred)(int)); + +/// +///@brief Split the string view at the first occurance of c. (destructive, sv +/// will be the "second half" after the split) +/// +///@param StringView *sv the string view to split at. Will be the part after the +/// first _c_ if no c found this is empty. +///@param char c the character to split at +/// +///@return StringView the part before _c_, of no _c_ found this will be equal to +/// sv +/// +StringView sv_split_at(StringView *sv, char c); + +/// +///@brief Check of two string_views have equal contents +/// +///@param StringView a +///@param StringView b +/// +///@return int non-zero if a is equal to b +/// +int sv_eq(StringView a, StringView b); + +/// +///@brief Like sv_split_at but using sep as a separator (destructive) +/// +///@param StringView *sv the string to split. Will contain the second half (can +/// be empty if no sep found) +///@param StringView sep +/// +///@return StringView will be the first half (can be equal to sv, if no sep +/// found) +/// +StringView sv_split_at_sv(StringView *sv, StringView sep); + +/// +///@brief Create a null-terminated string from the sv, by copying the contents. +/// Must be freed +/// +///@return char the new c-string, must be freed +/// +char *sv_clone(StringView sv); + +/// +///@brief Concatenate a list of string views with a separator. The result must +///be freed +/// +///@return char a null-terminated string containing all svs separated with sep. +///Must be freed +/// +char *sv_concat_with_sep(const StringView *svs, size_t n, StringView sep); + +#endif + +#ifdef CLIBS_STRING_VIEW_IMPL + +size_t sv_len(StringView sv) { return sv.end - sv.data; } + +StringView sv_new_sized(const char *str, size_t len) { + return (StringView){ + .data = str, + .end = str + len, + }; +} + +StringView sv_new(const char *str) { return sv_new_sized(str, strlen(str)); } + +StringView sv_drop(StringView sv, size_t n) { + return (StringView){ + .data = sv.data + n, + .end = sv.end, + }; +} + +StringView sv_take(StringView sv, size_t n) { + return (StringView){ + .data = sv.data, + .end = sv.data + n < sv.end ? sv.data + n : sv.end, + }; +} + +StringView sv_shrink(StringView sv, size_t n) { + return (StringView){ + .data = sv.data, + .end = sv.end - n, + }; +} + +void sv_puts(StringView sv) { + while (sv.data < sv.end) { + putc(*sv.data++, stdout); + } +} + +StringView sv_seek(StringView sv, char c) { + while (sv.data < sv.end && *sv.data != c) { + sv.data++; + } + + return sv; +} + +StringView sv_seek_back(StringView sv, char c) { + while (sv.data < sv.end && *(sv.end - 1) != c) { + sv.end--; + } + + return sv; +} + +StringView sv_trim_front(StringView sv, int (*pred)(int)) { + while (sv.data < sv.end && pred(*sv.data)) { + sv.data++; + } + + return sv; +} + +StringView sv_trim_back(StringView sv, int (*pred)(int)) { + while (sv.data < sv.end && pred(*(sv.end - 1))) { + sv.end--; + } + + return sv; +} + +StringView sv_trim(StringView sv, int (*pred)(int)) { + return sv_trim_front(sv_trim_back(sv, pred), pred); +} + +StringView sv_split_at(StringView *sv, char c) { + StringView current = *sv; + + while (sv->data < sv->end && *sv->data != c) { + sv->data++; + } + + // Remove sep, current is the first part, sv the second + if (sv->data < sv->end) { + current.end = sv->data++; + } + + return current; +} + +int sv_eq(StringView a, StringView b) { + if (sv_len(a) != sv_len(b)) { + return 0; + } + + while (a.data < a.end) { + if (*a.data != *b.data) { + return 0; + } + + a.data++; + b.data++; + } + + return 1; +} + +StringView sv_split_at_sv(StringView *sv, StringView sep) { + StringView current = *sv; + size_t sep_len = sv_len(sep); + + while (sv->data < sv->end && !sv_eq(sv_take(*sv, sep_len), sep)) { + sv->data++; + } + + // Remove sep, current is the first part, sv the second + if (sv_len(*sv) >= sep_len) { + current.end = sv->data; + sv->data += sep_len; + } else { + sv->data = sv->end; + } + + return current; +} + +char *sv_clone(StringView sv) { + size_t n = sv_len(sv) + 1; + char *data = malloc(n); + + data[n - 1] = 0; + memcpy(data, sv.data, n - 1); + + return data; +} + +char *sv_concat_with_sep(const StringView *svs, size_t n, StringView sep) { + size_t len = 1; // Reserve nullterminator + size_t sep_len = sv_len(sep); + + for (int i = 0; i < n; i++) { + len += sv_len(svs[i]); + } + len += sep_len * (n - 1); + + char *data = malloc(len); + char *write_head = data; + + for (int i = 0; i < n; i++) { + if (i > 0) { + memcpy(write_head, sep.data, sep_len); + write_head += sep_len; + } + + size_t l = sv_len(svs[i]); + memcpy(wri + write_head += l; + } + + return data; +} + +#endif diff --git a/src/string-view.c b/src/string-view.c new file mode 100644 index 0000000..c7d2914 --- /dev/null +++ b/src/string-view.c @@ -0,0 +1,2 @@ +#define CLIBS_STRING_VIEW_IMPL +#include "c-libs/string-view.h"