Browse Source

Merge pull request #4190 from Kelimion/strings.cut

strings.cut without allocation.
Jeroen van Rijn 11 months ago
parent
commit
3da77bcd67
2 changed files with 60 additions and 48 deletions
  1. 53 42
      core/strings/strings.odin
  2. 7 6
      tests/core/strings/test_core_strings.odin

+ 53 - 42
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) {
 concatenate_safe :: proc(a: []string, allocator := context.allocator) -> (res: string, err: mem.Allocator_Error) {
 	return concatenate(a, allocator)
 	return concatenate(a, allocator)
 }
 }
+
 /*
 /*
 Returns a substring of the input string `s` with the specified rune offset and length
 Returns a substring of the input string `s` with the specified rune offset and length
 
 
-*Allocates Using Provided Allocator*
-
 Inputs:
 Inputs:
 - s: The input string to cut
 - s: The input string to cut
 - rune_offset: The starting rune index (default is 0). In runes, not bytes.
 - 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.
 - 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:
 Returns:
 - res: The substring
 - res: The substring
-- err: An optional allocator error if one occured, `nil` otherwise
 
 
 Example:
 Example:
 
 
@@ -743,57 +740,71 @@ Output:
 	example
 	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
 	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
 Splits the input string `s` into a slice of substrings separated by the specified `sep` string
 
 

+ 7 - 6
tests/core/strings/test_core_strings.odin

@@ -48,18 +48,19 @@ Cut_Test :: struct {
 }
 }
 
 
 cut_tests :: []Cut_Test{
 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
 test_cut :: proc(t: ^testing.T) {
 test_cut :: proc(t: ^testing.T) {
 	for test in cut_tests {
 	for test in cut_tests {
 		res := strings.cut(test.input, test.offset, test.length)
 		res := strings.cut(test.input, test.offset, test.length)
-		defer delete(res)
 
 
 		testing.expectf(
 		testing.expectf(
 			t,
 			t,