feat(sv): add string view lib

This commit is contained in:
2026-04-12 16:54:06 +02:00
parent a08ebbf3b4
commit a2a91bbce9
4 changed files with 406 additions and 1 deletions

View File

@@ -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 $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
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)

49
app/strings.c Normal file
View File

@@ -0,0 +1,49 @@
#include "c-libs/dyn-arr.h"
#include "c-libs/string-view.h"
#include <ctype.h>
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);
}

View File

@@ -0,0 +1,350 @@
#ifndef CLIBS_STRING_VIEW_H
#define CLIBS_STRING_VIEW_H
#include <malloc.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
/// 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

2
src/string-view.c Normal file
View File

@@ -0,0 +1,2 @@
#define CLIBS_STRING_VIEW_IMPL
#include "c-libs/string-view.h"