From 99380647baacffb9a9d94ce29d37d71554bc3b1b Mon Sep 17 00:00:00 2001 From: frapank Date: Sat, 4 Jul 2026 12:30:31 +0200 Subject: [PATCH] rewrite: dstrrange with in place modification --- dstr.c | 47 +++++++++++++++---------- dstr.h | 14 ++++---- tester.c | 105 ++++++++++++++++++++++++++++--------------------------- 3 files changed, 89 insertions(+), 77 deletions(-) diff --git a/dstr.c b/dstr.c index bc29b81..5864277 100644 --- a/dstr.c +++ b/dstr.c @@ -187,31 +187,42 @@ ssize_t dstrfind(dstr s, const char* needle) } // dstrrange -dstr dstrrange(dstr s, ssize_t start, ssize_t end) +void dstrrange(dstr s, ssize_t start, ssize_t end) { - if (!s) - return NULL; + enum dstrhd_type t; + void* hd = _dstr_get_hdr_and_type(s, &t); + if (!hd) + return; - size_t len_u = dstrlen(s); - ssize_t len = (len_u > (size_t)PTRDIFF_MAX) ? PTRDIFF_MAX : (ssize_t)len_u; + ssize_t len = (ssize_t)dstrlen(s); + if (len == 0) + return; - if (start < 0) + if (start < 0) { start += len; - if (start < 0) - start = 0; - if (start > len) - start = len; - - if (end < 0) + if (start < 0) + start = 0; + } + if (end < 0) { end += len; - if (end < 0) - end = 0; - if (end > len) - end = len; + if (end < 0) + end = 0; + } - size_t sub_len = (start >= end) ? 0 : (size_t)(end - start); + size_t newlen = (start > end) ? 0 : (size_t)(end - start) + 1; + if (newlen != 0) { + if (start >= len) { + newlen = 0; + } else if (end >= len) { + end = len - 1; + newlen = (size_t)(end - start) + 1; + } + } - return _dstr_slice_alloc(s + start, sub_len); + if (start && newlen) + memmove(s, s + start, newlen); + s[newlen] = '\0'; + _dstr_set_len(hd, newlen, t); } // dstrsplit diff --git a/dstr.h b/dstr.h index d3f3eb4..10792b2 100644 --- a/dstr.h +++ b/dstr.h @@ -129,14 +129,14 @@ ssize_t dstrfind(dstr s, const char* needle) W_UNUSED_RESULT; /* * dstrrange(s, start, end) * - * Return a new dstr with a copy of the [start, end) slice of s (end is - * exclusive). Negative indices count from the end of the string, as in - * s[len + start]. Out-of-range indices are clamped to [0, len], and a - * start at or past end yields an empty (but non-NULL) dstr. - * Returns NULL if s is NULL or on allocation failure. The result is an - * independent allocation; free it with dstrfree. + * Keep only the [start, end] slice of s, in place (end is inclusive). + * Negative indices count from the end of the string, as in s[len + start]. + * Out-of-range indices are clamped, and a start past end or past the end + * of the string yields an empty string. No allocation, no new pointer: + * the existing buffer is shifted with memmove and truncated in place. + * No-op if s is NULL or empty. */ -dstr dstrrange(dstr s, ssize_t start, ssize_t end) W_UNUSED_RESULT; +void dstrrange(dstr s, ssize_t start, ssize_t end); /* * dstrsplit(s, delim, out_count) diff --git a/tester.c b/tester.c index 7447fd8..43e59a3 100644 --- a/tester.c +++ b/tester.c @@ -220,41 +220,41 @@ static void test_case_conversion_roundtrip(void) { static void test_range_basic(void) { dstr s = dstrnew("Hello World"); - dstr sub = dstrrange(s, 0, 5); - ASSERT_STR_EQUAL("Hello", sub); - ASSERT_TRUE(dstrlen(sub) == 5); - dstrfree(sub); - - sub = dstrrange(s, 6, 11); - ASSERT_STR_EQUAL("World", sub); - dstrfree(sub); + dstrrange(s, 0, 4); + ASSERT_STR_EQUAL("Hello", s); + ASSERT_TRUE(dstrlen(s) == 5); + dstrfree(s); - sub = dstrrange(s, 0, 11); - ASSERT_STR_EQUAL("Hello World", sub); - dstrfree(sub); + s = dstrnew("Hello World"); + dstrrange(s, 6, 10); + ASSERT_STR_EQUAL("World", s); + dstrfree(s); + s = dstrnew("Hello World"); + dstrrange(s, 0, 10); + ASSERT_STR_EQUAL("Hello World", s); dstrfree(s); } static void test_range_negative_index(void) { dstr s = dstrnew("Hello World"); + dstrrange(s, -5, -2); + ASSERT_STR_EQUAL("Worl", s); + dstrfree(s); - dstr sub = dstrrange(s, -5, -1); - ASSERT_STR_EQUAL("Worl", sub); - dstrfree(sub); - - sub = dstrrange(s, -11, -6); - ASSERT_STR_EQUAL("Hello", sub); - dstrfree(sub); - - sub = dstrrange(s, 0, -1); - ASSERT_STR_EQUAL("Hello Worl", sub); - dstrfree(sub); + s = dstrnew("Hello World"); + dstrrange(s, -11, -7); + ASSERT_STR_EQUAL("Hello", s); + dstrfree(s); - sub = dstrrange(s, -5, 11); - ASSERT_STR_EQUAL("World", sub); - dstrfree(sub); + s = dstrnew("Hello World"); + dstrrange(s, 0, -2); + ASSERT_STR_EQUAL("Hello Worl", s); + dstrfree(s); + s = dstrnew("Hello World"); + dstrrange(s, -5, 10); + ASSERT_STR_EQUAL("World", s); dstrfree(s); } @@ -262,51 +262,52 @@ static void test_range_out_of_bounds(void) { dstr s = dstrnew("abc"); // Indices past either end are clamped, not an error. - dstr sub = dstrrange(s, -100, 100); - ASSERT_STR_EQUAL("abc", sub); - dstrfree(sub); - - sub = dstrrange(s, 5, 10); - ASSERT_STR_EQUAL("", sub); - ASSERT_TRUE(dstrlen(sub) == 0); - dstrfree(sub); + dstrrange(s, -100, 100); + ASSERT_STR_EQUAL("abc", s); + dstrfree(s); - sub = dstrrange(s, 2, 1); - ASSERT_STR_EQUAL("", sub); - dstrfree(sub); + s = dstrnew("abc"); + dstrrange(s, 5, 10); + ASSERT_STR_EQUAL("", s); + ASSERT_TRUE(dstrlen(s) == 0); + dstrfree(s); - sub = dstrrange(s, -100, -50); - ASSERT_STR_EQUAL("", sub); - dstrfree(sub); + s = dstrnew("abc"); + dstrrange(s, 2, 1); + ASSERT_STR_EQUAL("", s); + dstrfree(s); + // Both indices resolve to a clamped 0 rather than an empty range: this + // mirrors sdsrange, whose clamp-to-0 path (unlike its clamp-to-len + // path) does not force newlen to 0. + s = dstrnew("abc"); + dstrrange(s, -100, -50); + ASSERT_STR_EQUAL("a", s); dstrfree(s); } static void test_range_null(void) { - dstr sub = dstrrange(NULL, 0, 5); - ASSERT_TRUE(sub == NULL); + dstrrange(NULL, 0, 5); } static void test_range_empty_string(void) { dstr s = dstrnew(""); - dstr sub = dstrrange(s, -5, 5); - ASSERT_STR_EQUAL("", sub); - dstrfree(sub); + dstrrange(s, -5, 5); + ASSERT_STR_EQUAL("", s); dstrfree(s); } -static void test_range_independent_allocation(void) { - // Mutating the slice must not affect the source string. - dstr s = dstrnew("Hello"); - dstr sub = dstrrange(s, 0, 5); +static void test_range_in_place(void) { + // dstrrange mutates the existing buffer, no new allocation. + dstr s = dstrnew("Hello World"); + dstr same = s; - dstrtoupper(sub); + dstrrange(s, 0, 4); + ASSERT_TRUE(s == same); ASSERT_STR_EQUAL("Hello", s); - ASSERT_STR_EQUAL("HELLO", sub); dstrfree(s); - dstrfree(sub); } static void test_split_basic(void) { @@ -476,7 +477,7 @@ int main(void) { RUN_TEST(test_range_out_of_bounds); RUN_TEST(test_range_null); RUN_TEST(test_range_empty_string); - RUN_TEST(test_range_independent_allocation); + RUN_TEST(test_range_in_place); RUN_TEST(test_split_basic); RUN_TEST(test_split_adjacent_and_edge_delimiters); RUN_TEST(test_split_multichar_delim);