Skip to content

Commit d082015

Browse files
committed
perf: fix IndexAt() to extract runes from segment directly
The previous implementation tracked byte lengths but compared against a rune index, then called s.Runes()[idx] which reconverted the entire string to runes - O(n) per index lookup. Now we track cumulative rune lengths and extract the character directly from the segment, making IndexAt() O(n) for the first call and avoiding the redundant full-string conversion.
1 parent 273beb2 commit d082015

File tree

2 files changed

+20
-5
lines changed

2 files changed

+20
-5
lines changed

core/testing/string_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,16 @@ print(a[len(a) - 3])
3333
assertOnlyOutput(t, stdOutBuffer, "i\n")
3434
assertNoErrors(t)
3535
}
36+
37+
func TestString_MultiByteIndexing(t *testing.T) {
38+
script := `
39+
s = "a😀b"
40+
print(s[0])
41+
print(s[1])
42+
print(s[2])
43+
print(s[-1])
44+
`
45+
setupAndRunCode(t, script, "--color=never")
46+
assertOnlyOutput(t, stdOutBuffer, "a\n😀\nb\nb\n")
47+
assertNoErrors(t)
48+
}

core/type_string.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,16 @@ func (s *RadString) Index(i *Interpreter, idxNode *ts.Node) RadString {
120120

121121
// assumes idx is valid for this string
122122
func (s *RadString) IndexAt(idx int64) RadString {
123-
cumLen := 0
123+
cumRuneLen := int64(0)
124124
for _, segment := range s.Segments {
125-
nextSegmentLen := len(segment.String)
126-
if cumLen+nextSegmentLen > int(idx) {
127-
char := s.Runes()[idx] // todo inefficient, should just look up in segment
125+
segmentRunes := []rune(segment.String)
126+
segmentRuneLen := int64(len(segmentRunes))
127+
if cumRuneLen+segmentRuneLen > idx {
128+
offsetInSegment := idx - cumRuneLen
129+
char := segmentRunes[offsetInSegment]
128130
return newRadStringWithAttr(string(char), segment)
129131
}
130-
cumLen += +nextSegmentLen
132+
cumRuneLen += segmentRuneLen
131133
}
132134
RP.RadErrorExit("Bug! IndexAt called with invalid index")
133135
panic(UNREACHABLE)

0 commit comments

Comments
 (0)