diff --git a/lib/std/core/dstring.c3 b/lib/std/core/dstring.c3 index 267c9d8de..2a712dd3a 100644 --- a/lib/std/core/dstring.c3 +++ b/lib/std/core/dstring.c3 @@ -96,7 +96,7 @@ fn void DString.chop(self, usz new_size) fn String DString.str_view(self) { - StringData* data = (StringData*)self; + StringData* data = self.data(); if (!data) return ""; return (String)data.chars[:data.len]; } @@ -267,7 +267,7 @@ fn Char32[] DString.copy_utf32(&self, Allocator* using = mem::heap()) fn void DString.append_string(&self, DString str) { - StringData* other = (StringData*)str; + StringData* other = str.data(); if (!other) return; self.append(str.str_view()); } @@ -315,6 +315,37 @@ macro void DString.append(&self, value) $endswitch } +fn void DString.insert_at(&self, usz index, String s) +{ + if (s.len == 0) return; + self.reserve(s.len); + StringData* data = self.data(); + usz len = self.len(); + if (data.chars[:len].ptr == s.ptr) + { + // Source and destination are the same: nothing to do. + return; + } + index = min(index, len); + data.len += s.len; + + char* start = data.chars[index:s.len].ptr; // area to insert into + mem::move(start + s.len, start, len - index); // move existing data + switch + { + case s.ptr <= start && start < s.ptr + s.len: + // Overlapping areas. + foreach_r (i, c : s) + { + data.chars[index + i] = c; + } + case start <= s.ptr && s.ptr < start + len: + // Source has moved. + mem::move(start, s.ptr + s.len, s.len); + default: + mem::move(start, s, s.len); + } +} fn usz! DString.printf(&self, String format, args...) @maydiscard { diff --git a/test/unit/stdlib/core/dstring.c3 b/test/unit/stdlib/core/dstring.c3 new file mode 100644 index 000000000..b8cfb879b --- /dev/null +++ b/test/unit/stdlib/core/dstring.c3 @@ -0,0 +1,64 @@ +module std::core::dstring::tests @test; + +fn void test_insert_at() +{ + DString str = dstring::tnew(" world"); + String s; + + str.insert_at(0, ""); + s = str.str_view(); + assert(s == " world", "got '%s'; want ' world'", s); + + str.insert_at(0, "hello"); + s = str.str_view(); + assert(s == "hello world", "got '%s'; want 'hello world'", s); + + str.insert_at(5, " shiny"); + s = str.str_view(); + assert(s == "hello shiny world", "got '%s'; want 'hello shiny world'", s); +} + +fn void test_insert_at_overlaps() +{ + DString str = dstring::tnew("abc"); + String s; + String v; + + str.insert_at(0, "bc"); + s = str.str_view(); + assert(s == "bcabc", "got '%s'; want 'bcabc'", s); + + // Inserted string is unchanged. + str.chop(0); + str.append("abc"); + v = str.str_view(); + str.insert_at(0, v); + s = str.str_view(); + assert(s == "abc", "got '%s'; want 'abc'", s); + + // Inserted string is part of the tail. + str.chop(0); + str.append("abc"); + v = str.str_view()[1..]; + assert(v == "bc"); + str.insert_at(0, v); + s = str.str_view(); + assert(s == "bcabc", "got '%s'; want 'bcabc'", s); + + // Inserted string is part of the head. + str.chop(0); + str.append("abc"); + v = str.str_view()[1..]; + str.insert_at(2, v); + s = str.str_view(); + assert(s == "abbcc", "got '%s'; want 'abbcc'", s); + + str.chop(0); + str.append("abcdef"); + v = str.str_view()[3..]; + assert(v == "def"); + str.insert_at(0, v); + str.chop(3); + s = str.str_view(); + assert(s == "def", "got '%s'; want 'def'", s); +} \ No newline at end of file