aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2024-09-03 22:47:56 +0200
committerGitHub <noreply@github.com>2024-09-03 22:47:56 +0200
commit645207b8b07cd2d8b58f46f0a4748ac0a0d0e3d5 (patch)
tree481a55b91545c0b133ef4ba05954a09c234adc89
parent6bbc165121f94d2ad5c6ebba676c5b8137661a05 (diff)
parent597ba796b7340aeca0aa25ac7d7f52ca2411294a (diff)
Merge pull request #4192 from laytan/strings-substring-rune-wise
strings: add `substring`, `substring_to` and `substring_from`
-rw-r--r--core/strings/strings.odin103
-rw-r--r--tests/core/strings/test_core_strings.odin33
2 files changed, 135 insertions, 1 deletions
diff --git a/core/strings/strings.odin b/core/strings/strings.odin
index dfe881ccd..b69c4a0e0 100644
--- a/core/strings/strings.odin
+++ b/core/strings/strings.odin
@@ -3327,3 +3327,106 @@ levenshtein_distance :: proc(a, b: string, allocator := context.allocator, loc :
return costs[n], nil
}
+
+@(private)
+internal_substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) {
+ sub = s
+ ok = true
+
+ rune_i: int
+
+ if rune_start > 0 {
+ ok = false
+ for _, i in sub {
+ if rune_start == rune_i {
+ ok = true
+ sub = sub[i:]
+ break
+ }
+ rune_i += 1
+ }
+ if !ok { return }
+ }
+
+ if rune_end >= rune_start {
+ ok = false
+ for _, i in sub {
+ if rune_end == rune_i {
+ ok = true
+ sub = sub[:i]
+ break
+ }
+ rune_i += 1
+ }
+
+ if rune_end == rune_i {
+ ok = true
+ }
+ }
+
+ return
+}
+
+/*
+Returns a substring of `s` that starts at rune index `rune_start` and goes up to `rune_end`.
+
+Think of it as slicing `s[rune_start:rune_end]` but rune-wise.
+
+Inputs:
+- s: the string to substring
+- rune_start: the start (inclusive) rune
+- rune_end: the end (exclusive) rune
+
+Returns:
+- sub: the substring
+- ok: whether the rune indexes where in bounds of the original string
+*/
+substring :: proc(s: string, rune_start: int, rune_end: int) -> (sub: string, ok: bool) {
+ if rune_start < 0 || rune_end < 0 || rune_end < rune_start {
+ return
+ }
+
+ return internal_substring(s, rune_start, rune_end)
+}
+
+/*
+Returns a substring of `s` that starts at rune index `rune_start` and goes up to the end of the string.
+
+Think of it as slicing `s[rune_start:]` but rune-wise.
+
+Inputs:
+- s: the string to substring
+- rune_start: the start (inclusive) rune
+
+Returns:
+- sub: the substring
+- ok: whether the rune indexes where in bounds of the original string
+*/
+substring_from :: proc(s: string, rune_start: int) -> (sub: string, ok: bool) {
+ if rune_start < 0 {
+ return
+ }
+
+ return internal_substring(s, rune_start, -1)
+}
+
+/*
+Returns a substring of `s` that goes up to rune index `rune_end`.
+
+Think of it as slicing `s[:rune_end]` but rune-wise.
+
+Inputs:
+- s: the string to substring
+- rune_end: the end (exclusive) rune
+
+Returns:
+- sub: the substring
+- ok: whether the rune indexes where in bounds of the original string
+*/
+substring_to :: proc(s: string, rune_end: int) -> (sub: string, ok: bool) {
+ if rune_end < 0 {
+ return
+ }
+
+ return internal_substring(s, -1, rune_end)
+}
diff --git a/tests/core/strings/test_core_strings.odin b/tests/core/strings/test_core_strings.odin
index 302980299..0d94b9c62 100644
--- a/tests/core/strings/test_core_strings.odin
+++ b/tests/core/strings/test_core_strings.odin
@@ -124,4 +124,35 @@ test_case_conversion :: proc(t: ^testing.T) {
testing.expectf(t, result == entry.s, "ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result)
}
}
-} \ No newline at end of file
+}
+
+@(test)
+test_substring :: proc(t: ^testing.T) {
+ Case :: struct {
+ s: string,
+ start: int,
+ end: int,
+ sub: string,
+ ok: bool,
+ }
+ cases := []Case {
+ {ok = true},
+ {s = "", start = -1, ok = false},
+ {s = "", end = -1, ok = false},
+ {s = "", end = +1, ok = false},
+ {s = "Hello", end = len("Hello"), sub = "Hello", ok = true},
+ {s = "Hello", start = 1, end = len("Hello"), sub = "ello", ok = true},
+ {s = "Hello", start = 1, end = len("Hello") - 1, sub = "ell", ok = true},
+ {s = "Hello", end = len("Hello") + 1, sub = "Hello", ok = false},
+ {s = "小猫咪", start = 0, end = 3, sub = "小猫咪", ok = true},
+ {s = "小猫咪", start = 1, end = 3, sub = "猫咪", ok = true},
+ {s = "小猫咪", start = 1, end = 5, sub = "猫咪", ok = false},
+ {s = "小猫咪", start = 1, end = 1, sub = "", ok = true},
+ }
+
+ for tc in cases {
+ sub, ok := strings.substring(tc.s, tc.start, tc.end)
+ testing.expectf(t, ok == tc.ok, "expected %v[%v:%v] to return ok: %v", tc.s, tc.start, tc.end, tc.ok)
+ testing.expectf(t, sub == tc.sub, "expected %v[%v:%v] to return sub: %v, got: %v", tc.s, tc.start, tc.end, tc.sub, sub)
+ }
+}