diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2024-09-03 19:02:40 +0200 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2024-09-03 19:02:40 +0200 |
| commit | 996175753c555d5c035e2c8f11312c4eedf7339d (patch) | |
| tree | 51b401c1167e57e0ab21330dc1409ecaa64f2988 | |
| parent | 0e6109e171d24b3bb17289219ae3b482c24f2460 (diff) | |
strings.cut without allocation.
| -rw-r--r-- | core/strings/strings.odin | 95 | ||||
| -rw-r--r-- | tests/core/strings/test_core_strings.odin | 13 |
2 files changed, 60 insertions, 48 deletions
diff --git a/core/strings/strings.odin b/core/strings/strings.odin index 216d7ad79..dfe881ccd 100644 --- a/core/strings/strings.odin +++ b/core/strings/strings.odin @@ -710,20 +710,17 @@ The concatenated string, and an error if allocation fails concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) { return concatenate(a, allocator) } + /* Returns a substring of the input string `s` with the specified rune offset and length -*Allocates Using Provided Allocator* - Inputs: - s: The input string to cut - rune_offset: The starting rune index (default is 0). In runes, not bytes. - rune_length: The number of runes to include in the substring (default is 0, which returns the remainder of the string). In runes, not bytes. -- allocator: (default is context.allocator) Returns: - res: The substring -- err: An optional allocator error if one occured, `nil` otherwise Example: @@ -743,57 +740,71 @@ Output: example */ -cut :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { +cut :: proc(s: string, rune_offset := int(0), rune_length := int(0)) -> (res: string) { s := s; rune_length := rune_length - context.allocator = allocator - // If we signal that we want the entire remainder (length <= 0) *and* - // the offset is zero, then we can early out by cloning the input - if rune_offset == 0 && rune_length <= 0 { - return clone(s) + count := 0 + for _, offset in s { + if count == rune_offset { + s = s[offset:] + break + } + count += 1 } - // We need to know if we have enough runes to cover offset + length. - rune_count := utf8.rune_count_in_string(s) - - // We're asking for a substring starting after the end of the input string. - // That's just an empty string. - if rune_offset >= rune_count { - return "", nil + if rune_length <= 1 { + return s } - // If we don't specify the length of the substring, use the remainder. - if rune_length <= 0 { - rune_length = rune_count - rune_offset + count = 0 + for _, offset in s { + if count == rune_length { + s = s[:offset] + break + } + count += 1 } + return s +} - // We don't yet know how many bytes we need exactly. - // But we do know it's bounded by the number of runes * 4 bytes, - // and can be no more than the size of the input string. - bytes_needed := min(rune_length * 4, len(s)) - buf := make([]u8, bytes_needed, allocator, loc) or_return +/* +Returns a substring of the input string `s` with the specified rune offset and length - byte_offset := 0 - for i := 0; i < rune_count; i += 1 { - _, w := utf8.decode_rune_in_string(s) +*Allocates Using Provided Allocator* - // If the rune is part of the substring, copy it to the output buffer. - if i >= rune_offset { - for j := 0; j < w; j += 1 { - buf[byte_offset+j] = s[j] - } - byte_offset += w - } +Inputs: +- s: The input string to cut +- rune_offset: The starting rune index (default is 0). In runes, not bytes. +- rune_length: The number of runes to include in the substring (default is 0, which returns the remainder of the string). In runes, not bytes. +- allocator: (default is context.allocator) - // We're done if we reach the end of the input string, *or* - // if we've reached a specified length in runes. - if rune_length > 0 { - if i == rune_offset + rune_length - 1 { break } - } - s = s[w:] +Returns: +- res: The substring +- err: An optional allocator error if one occured, `nil` otherwise + +Example: + + import "core:fmt" + import "core:strings" + + cut_example :: proc() { + fmt.println(strings.cut_clone("some example text", 0, 4)) // -> "some" + fmt.println(strings.cut_clone("some example text", 2, 2)) // -> "me" + fmt.println(strings.cut_clone("some example text", 5, 7)) // -> "example" } - return string(buf[:byte_offset]), nil + +Output: + + some + me + example + +*/ +cut_clone :: proc(s: string, rune_offset := int(0), rune_length := int(0), allocator := context.allocator, loc := #caller_location) -> (res: string, err: mem.Allocator_Error) #optional_allocator_error { + res = cut(s, rune_offset, rune_length) + return clone(res, allocator, loc) } + /* Splits the input string `s` into a slice of substrings separated by the specified `sep` string diff --git a/tests/core/strings/test_core_strings.odin b/tests/core/strings/test_core_strings.odin index 0ee2b3eb9..302980299 100644 --- a/tests/core/strings/test_core_strings.odin +++ b/tests/core/strings/test_core_strings.odin @@ -48,18 +48,19 @@ Cut_Test :: struct { } cut_tests :: []Cut_Test{ - {"some example text", 0, 4, "some" }, - {"some example text", 2, 2, "me" }, - {"some example text", 5, 7, "example" }, - {"some example text", 5, 0, "example text"}, - {"恥ずべきフクロウ", 4, 0, "フクロウ" }, + {"some example text", 0, 0, "some example text" }, + {"some example text", 0, 4, "some" }, + {"some example text", 2, 2, "me" }, + {"some example text", 5, 7, "example" }, + {"some example text", 5, 0, "example text" }, + {"恥ずべきフクロウ", 0, 0, "恥ずべきフクロウ" }, + {"恥ずべきフクロウ", 4, 0, "フクロウ" }, } @test test_cut :: proc(t: ^testing.T) { for test in cut_tests { res := strings.cut(test.input, test.offset, test.length) - defer delete(res) testing.expectf( t, |