Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions dstr.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions dstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
105 changes: 53 additions & 52 deletions tester.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,93 +220,94 @@ 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);
}

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) {
Expand Down Expand Up @@ -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);
Expand Down
Loading