Skip to content

Commit a1167be

Browse files
committed
Optimize slice and string indexing paths
1 parent 606b7a8 commit a1167be

2 files changed

Lines changed: 83 additions & 17 deletions

File tree

conversions.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,29 +107,36 @@ func stringLength(v string) int {
107107
return utf8.RuneCountInString(v)
108108
}
109109

110-
func runeIndexToByteOffset(v string, idx int) int {
111-
if idx <= 0 {
112-
return 0
110+
func runeRangeToByteOffsets(v string, startIdx, endIdx int) (int, int) {
111+
if startIdx <= 0 {
112+
startIdx = 0
113+
}
114+
if endIdx < startIdx {
115+
endIdx = startIdx
113116
}
114117

115118
offset := 0
116-
for i := 0; i < idx && offset < len(v); i++ {
119+
for i := 0; i < startIdx && offset < len(v); i++ {
120+
_, size := utf8.DecodeRuneInString(v[offset:])
121+
offset += size
122+
}
123+
124+
startOffset := offset
125+
for i := startIdx; i < endIdx && offset < len(v); i++ {
117126
_, size := utf8.DecodeRuneInString(v[offset:])
118127
offset += size
119128
}
120129

121-
return offset
130+
return startOffset, offset
122131
}
123132

124133
func stringIndex(v string, idx int) string {
125-
start := runeIndexToByteOffset(v, idx)
126-
end := runeIndexToByteOffset(v, idx+1)
134+
start, end := runeRangeToByteOffsets(v, idx, idx+1)
127135
return v[start:end]
128136
}
129137

130138
func stringSlice(v string, start, end int) string {
131-
from := runeIndexToByteOffset(v, start)
132-
to := runeIndexToByteOffset(v, end+1)
139+
from, to := runeRangeToByteOffsets(v, start, end+1)
133140
return v[from:to]
134141
}
135142

interpreter.go

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,14 @@ func checkBounds(ast *Node, input any, idx int) Error {
3838
}
3939
}
4040
if v, ok := input.(string); ok {
41-
length := stringLength(v)
42-
if idx < 0 || idx >= length {
43-
return NewError(ast.Offset, ast.Length, "invalid index %d for string of length %d", int(idx), length)
44-
}
41+
return checkStringBounds(ast, stringLength(v), idx)
42+
}
43+
return nil
44+
}
45+
46+
func checkStringBounds(ast *Node, length, idx int) Error {
47+
if idx < 0 || idx >= length {
48+
return NewError(ast.Offset, ast.Length, "invalid index %d for string of length %d", idx, length)
4549
}
4650
return nil
4751
}
@@ -166,6 +170,60 @@ func (i *interpreter) run(ast *Node, value any) (any, Error) {
166170
if !isSlice(resultLeft) && !isString(resultLeft) {
167171
return nil, NewError(ast.Offset, ast.Length, "can only index strings or arrays but got %v", resultLeft)
168172
}
173+
if ast.Right != nil && ast.Right.Type == NodeSlice {
174+
startValue, err := i.run(ast.Right.Left, value)
175+
if err != nil {
176+
return nil, err
177+
}
178+
endValue, err := i.run(ast.Right.Right, value)
179+
if err != nil {
180+
return nil, err
181+
}
182+
start, err := toNumber(ast.Right.Left, startValue)
183+
if err != nil {
184+
return nil, err
185+
}
186+
end, err := toNumber(ast.Right.Right, endValue)
187+
if err != nil {
188+
return nil, err
189+
}
190+
if left, ok := resultLeft.([]any); ok {
191+
if start < 0 {
192+
start += float64(len(left))
193+
}
194+
if end < 0 {
195+
end += float64(len(left))
196+
}
197+
if err := checkBounds(ast, left, int(start)); err != nil {
198+
return nil, err
199+
}
200+
if err := checkBounds(ast, left, int(end)); err != nil {
201+
return nil, err
202+
}
203+
if int(start) > int(end) {
204+
return nil, NewError(ast.Offset, ast.Length, "slice start cannot be greater than end")
205+
}
206+
return left[int(start) : int(end)+1], nil
207+
}
208+
left := toString(resultLeft)
209+
leftLen := stringLength(left)
210+
if start < 0 {
211+
start += float64(leftLen)
212+
}
213+
if end < 0 {
214+
end += float64(leftLen)
215+
}
216+
if err := checkStringBounds(ast, leftLen, int(start)); err != nil {
217+
return nil, err
218+
}
219+
if int(start) > int(end) {
220+
return nil, NewError(ast.Offset, ast.Length, "string slice start cannot be greater than end")
221+
}
222+
if err := checkStringBounds(ast, leftLen, int(end)); err != nil {
223+
return nil, err
224+
}
225+
return stringSlice(left, int(start), int(end)), nil
226+
}
169227
resultRight, err := i.run(ast.Right, value)
170228
if err != nil {
171229
return nil, err
@@ -205,13 +263,13 @@ func (i *interpreter) run(ast *Node, value any) (any, Error) {
205263
if end < 0 {
206264
end += float64(leftLen)
207265
}
208-
if err := checkBounds(ast, left, int(start)); err != nil {
266+
if err := checkStringBounds(ast, leftLen, int(start)); err != nil {
209267
return nil, err
210268
}
211269
if int(start) > int(end) {
212270
return nil, NewError(ast.Offset, ast.Length, "string slice start cannot be greater than end")
213271
}
214-
if err := checkBounds(ast, left, int(end)); err != nil {
272+
if err := checkStringBounds(ast, leftLen, int(end)); err != nil {
215273
return nil, err
216274
}
217275
return stringSlice(left, int(start), int(end)), nil
@@ -231,10 +289,11 @@ func (i *interpreter) run(ast *Node, value any) (any, Error) {
231289
return left[int(idx)], nil
232290
}
233291
left := toString(resultLeft)
292+
leftLen := stringLength(left)
234293
if idx < 0 {
235-
idx += float64(stringLength(left))
294+
idx += float64(leftLen)
236295
}
237-
if err := checkBounds(ast, left, int(idx)); err != nil {
296+
if err := checkStringBounds(ast, leftLen, int(idx)); err != nil {
238297
return nil, err
239298
}
240299
return stringIndex(left, int(idx)), nil

0 commit comments

Comments
 (0)