Skip to content

Commit 7350604

Browse files
committed
feat: add ?? fallback support for list and string indexing
Out-of-bounds index access now throws a catchable RadPanic instead of a fatal error, enabling fallback syntax like `list[10] ?? "default"`. This mirrors the existing map key-not-found behavior, giving lists and strings consistent fallback semantics with maps.
1 parent c8373a2 commit 7350604

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

core/errors.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package core
22

33
import (
4+
"github.com/amterp/rad/rts/rl"
45
ts "github.com/tree-sitter/go-tree-sitter"
56
)
67

78
func ErrIndexOutOfBounds(i *Interpreter, node *ts.Node, idx int64, length int64) {
8-
i.errorf(node, "Index out of bounds: %d (length %d)", idx, length)
9+
errVal := newRadValue(i, node, NewErrorStrf("Index out of bounds: %d (length %d)", idx, length).SetCode(rl.ErrIndexOutOfBounds))
10+
i.NewRadPanic(node, errVal).Panic()
911
}
1012

1113
type RadPanic struct {

core/testing/array_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ a[4] = 500
152152
expected := `Error at L3:3
153153
154154
a[4] = 500
155-
^ Index out of bounds: 4 (length 4)
155+
^ Index out of bounds: 4 (length 4) (RAD20029)
156156
`
157157
assertError(t, 1, expected)
158158
}
@@ -200,7 +200,7 @@ print(a[-99])
200200
expected := `Error at L3:9
201201
202202
print(a[-99])
203-
^^^ Index out of bounds: -99 (length 4)
203+
^^^ Index out of bounds: -99 (length 4) (RAD20029)
204204
`
205205
assertError(t, 1, expected)
206206
}
@@ -213,7 +213,7 @@ a[-99] = 5
213213
expected := `Error at L3:3
214214
215215
a[-99] = 5
216-
^^^ Index out of bounds: -99 (length 4)
216+
^^^ Index out of bounds: -99 (length 4) (RAD20029)
217217
`
218218
setupAndRunCode(t, script, "--color=never")
219219
assertError(t, 1, expected)

core/testing/fallback_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,80 @@ fallback
158158
assertOnlyOutput(t, stdOutBuffer, expected)
159159
assertNoErrors(t)
160160
}
161+
162+
func Test_Fallback_ListIndex(t *testing.T) {
163+
script := `
164+
list = [1, 2, 3]
165+
print(list[10] ?? "default")
166+
`
167+
setupAndRunCode(t, script, "--color=never")
168+
assertOnlyOutput(t, stdOutBuffer, "default\n")
169+
assertNoErrors(t)
170+
}
171+
172+
func Test_Fallback_ListIndexChained(t *testing.T) {
173+
script := `
174+
list = [1, 2, 3]
175+
print(list[5] ?? list[10] ?? "default")
176+
`
177+
setupAndRunCode(t, script, "--color=never")
178+
assertOnlyOutput(t, stdOutBuffer, "default\n")
179+
assertNoErrors(t)
180+
}
181+
182+
func Test_Fallback_ListNegativeIndex(t *testing.T) {
183+
script := `
184+
list = [1, 2, 3]
185+
print(list[-10] ?? "default")
186+
`
187+
setupAndRunCode(t, script, "--color=never")
188+
assertOnlyOutput(t, stdOutBuffer, "default\n")
189+
assertNoErrors(t)
190+
}
191+
192+
func Test_Fallback_ListIndexExists(t *testing.T) {
193+
script := `
194+
list = [1, 2, 3]
195+
print(list[1] ?? "default")
196+
`
197+
setupAndRunCode(t, script, "--color=never")
198+
assertOnlyOutput(t, stdOutBuffer, "2\n")
199+
assertNoErrors(t)
200+
}
201+
202+
func Test_Fallback_ListNullValueVsOutOfBounds(t *testing.T) {
203+
script := `
204+
list = [null, 1, 2]
205+
print(list[0] ?? "fallback")
206+
print(list[10] ?? "fallback")
207+
`
208+
setupAndRunCode(t, script, "--color=never")
209+
expected := `null
210+
fallback
211+
`
212+
assertOnlyOutput(t, stdOutBuffer, expected)
213+
assertNoErrors(t)
214+
}
215+
216+
func Test_Fallback_ListEmpty(t *testing.T) {
217+
script := `
218+
print([][0] ?? "default")
219+
`
220+
setupAndRunCode(t, script, "--color=never")
221+
assertOnlyOutput(t, stdOutBuffer, "default\n")
222+
assertNoErrors(t)
223+
}
224+
225+
func Test_Fallback_StringIndex(t *testing.T) {
226+
script := `
227+
s = "hello"
228+
print(s[10] ?? "default")
229+
print(s[1] ?? "default")
230+
`
231+
setupAndRunCode(t, script, "--color=never")
232+
expected := `default
233+
e
234+
`
235+
assertOnlyOutput(t, stdOutBuffer, expected)
236+
assertNoErrors(t)
237+
}

rts/rl/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const (
5151
ErrStdinRead = "20026"
5252
ErrInvalidCheckDuration = "20027"
5353
ErrKeyNotFound = "20028"
54+
ErrIndexOutOfBounds = "20029"
5455

5556
// 3xxxx Type Errors?
5657

0 commit comments

Comments
 (0)