Skip to content
This repository was archived by the owner on Mar 16, 2025. It is now read-only.

Commit e2d0344

Browse files
committed
basic support for custom datatypes when traversing path expressions
1 parent 29b1f08 commit e2d0344

7 files changed

Lines changed: 527 additions & 36 deletions

File tree

pkg/eval/test/symbol_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
package test
55

66
import (
7+
"fmt"
78
"testing"
89

910
"go.xrstf.de/rudi/pkg/eval/types"
1011
"go.xrstf.de/rudi/pkg/lang/ast"
12+
"go.xrstf.de/rudi/pkg/pathexpr"
1113
"go.xrstf.de/rudi/pkg/testutil"
1214
)
1315

@@ -24,6 +26,39 @@ func makeSymbol(name string, path *ast.PathExpression) ast.Symbol {
2426
return sym
2527
}
2628

29+
type customType struct {
30+
Data string
31+
}
32+
33+
type customObjGetter struct {
34+
value any
35+
}
36+
37+
var _ pathexpr.ObjectReader = customObjGetter{}
38+
39+
func (g customObjGetter) GetObjectKey(name string) (any, error) {
40+
if name == "value" {
41+
return g.value, nil
42+
}
43+
44+
return nil, fmt.Errorf("cannot get property %q", name)
45+
}
46+
47+
type customVecGetter struct {
48+
magic int
49+
value any
50+
}
51+
52+
var _ pathexpr.VectorReader = customVecGetter{}
53+
54+
func (g customVecGetter) GetVectorItem(index int) (any, error) {
55+
if index == g.magic {
56+
return g.value, nil
57+
}
58+
59+
return nil, fmt.Errorf("cannot get index %d", index)
60+
}
61+
2762
func TestEvalSymbol(t *testing.T) {
2863
testcases := []testutil.Testcase{
2964
// <utterly invalid Symbol>
@@ -44,6 +79,14 @@ func TestEvalSymbol(t *testing.T) {
4479
},
4580
Expected: "foo",
4681
},
82+
// $var with custom data type
83+
{
84+
AST: makeSymbol("var", nil),
85+
Variables: types.Variables{
86+
"var": customType{Data: "foo"},
87+
},
88+
Expected: customType{Data: "foo"},
89+
},
4790
// $var.foo
4891
{
4992
AST: makeSymbol("var", &ast.PathExpression{Steps: []ast.Expression{ast.Identifier{Name: "foo"}}}),
@@ -86,6 +129,22 @@ func TestEvalSymbol(t *testing.T) {
86129
AST: makeSymbol("", &ast.PathExpression{}),
87130
Expected: nil,
88131
},
132+
// $custom.value
133+
{
134+
AST: makeSymbol("custom", &ast.PathExpression{Steps: []ast.Expression{ast.Identifier{Name: "value"}}}),
135+
Variables: types.Variables{
136+
"custom": customObjGetter{value: "foo"},
137+
},
138+
Expected: "foo",
139+
},
140+
// $custom[7]
141+
{
142+
AST: makeSymbol("custom", &ast.PathExpression{Steps: []ast.Expression{ast.Number{Value: 7}}}),
143+
Variables: types.Variables{
144+
"custom": customVecGetter{magic: 7, value: "foo"},
145+
},
146+
Expected: "foo",
147+
},
89148
}
90149

91150
for _, testcase := range testcases {

pkg/pathexpr/delete.go

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ import (
88
"fmt"
99
)
1010

11+
type ObjectKeyDeleter interface {
12+
DeleteObjectKey(name string) (any, error)
13+
}
14+
15+
type VectorItemDeleter interface {
16+
DeleteVectorItem(index int) (any, error)
17+
}
18+
1119
func removeSliceItem(slice []any, index int) []any {
1220
return append(slice[:index], slice[index+1:]...)
1321
}
@@ -24,18 +32,18 @@ func Delete(dest any, path Path) (any, error) {
2432
if len(remainingSteps) == 0 {
2533
// [index]
2634
if index, ok := toIntegerStep(thisStep); ok {
27-
if index < 0 {
28-
return nil, fmt.Errorf("index %d out of bounds", index)
29-
}
30-
3135
if slice, ok := dest.([]any); ok {
32-
if index >= len(slice) {
36+
if index < 0 || index >= len(slice) {
3337
return nil, fmt.Errorf("index %d out of bounds", index)
3438
}
3539

3640
return removeSliceItem(slice, index), nil
3741
}
3842

43+
if deleter, ok := dest.(VectorItemDeleter); ok {
44+
return deleter.DeleteVectorItem(index)
45+
}
46+
3947
return nil, fmt.Errorf("cannot delete index from %T", dest)
4048
}
4149

@@ -46,6 +54,10 @@ func Delete(dest any, path Path) (any, error) {
4654
return object, nil
4755
}
4856

57+
if deleter, ok := dest.(ObjectKeyDeleter); ok {
58+
return deleter.DeleteObjectKey(key)
59+
}
60+
4961
return nil, fmt.Errorf("cannot delete key from %T", dest)
5062
}
5163

@@ -54,12 +66,8 @@ func Delete(dest any, path Path) (any, error) {
5466

5567
// [index]...
5668
if index, ok := toIntegerStep(thisStep); ok {
57-
if index < 0 {
58-
return nil, fmt.Errorf("index %d out of bounds", index)
59-
}
60-
6169
if slice, ok := dest.([]any); ok {
62-
if index >= len(slice) {
70+
if index < 0 || index >= len(slice) {
6371
return nil, fmt.Errorf("index %d out of bounds", index)
6472
}
6573

@@ -75,6 +83,20 @@ func Delete(dest any, path Path) (any, error) {
7583
return slice, nil
7684
}
7785

86+
if writer, ok := dest.(VectorWriter); ok {
87+
existingValue, err := writer.GetVectorItem(index)
88+
if err != nil {
89+
return nil, fmt.Errorf("cannot descend with [%d] into %T", index, dest)
90+
}
91+
92+
updatedValue, err := Delete(existingValue, remainingSteps)
93+
if err != nil {
94+
return nil, err
95+
}
96+
97+
return writer.SetVectorItem(index, updatedValue)
98+
}
99+
78100
return nil, fmt.Errorf("cannot descend with [%d] into %T", index, dest)
79101
}
80102

@@ -94,6 +116,20 @@ func Delete(dest any, path Path) (any, error) {
94116
return object, nil
95117
}
96118

119+
if writer, ok := dest.(ObjectWriter); ok {
120+
existingValue, err := writer.GetObjectKey(key)
121+
if err != nil {
122+
return nil, fmt.Errorf("cannot descend with [%s] into %T", key, dest)
123+
}
124+
125+
updatedValue, err := Delete(existingValue, remainingSteps)
126+
if err != nil {
127+
return nil, err
128+
}
129+
130+
return writer.SetObjectKey(key, updatedValue)
131+
}
132+
97133
return nil, fmt.Errorf("cannot descend with [%s] into %T", key, dest)
98134
}
99135

pkg/pathexpr/delete_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,77 @@
44
package pathexpr
55

66
import (
7+
"fmt"
78
"testing"
89

910
"github.com/google/go-cmp/cmp"
1011
)
1112

13+
type customObjDeleter struct {
14+
Value any
15+
}
16+
17+
var _ ObjectKeyDeleter = &customObjDeleter{}
18+
19+
func (w customObjDeleter) GetObjectKey(name string) (any, error) {
20+
if name == "value" {
21+
return w.Value, nil
22+
}
23+
24+
return nil, fmt.Errorf("cannot get property %q", name)
25+
}
26+
27+
func (w *customObjDeleter) SetObjectKey(name string, value any) (any, error) {
28+
if name == "value" {
29+
w.Value = value
30+
return w, nil
31+
}
32+
33+
return nil, fmt.Errorf("cannot set property %q", name)
34+
}
35+
36+
func (w *customObjDeleter) DeleteObjectKey(name string) (any, error) {
37+
if name == "value" {
38+
w.Value = nil
39+
return w, nil
40+
}
41+
42+
return nil, fmt.Errorf("cannot delete property %q", name)
43+
}
44+
45+
type customVecDeleter struct {
46+
Magic int
47+
Value any
48+
}
49+
50+
var _ VectorItemDeleter = &customVecDeleter{}
51+
52+
func (w customVecDeleter) GetVectorItem(index int) (any, error) {
53+
if index == w.Magic {
54+
return w.Value, nil
55+
}
56+
57+
return nil, fmt.Errorf("cannot get index %d", index)
58+
}
59+
60+
func (w *customVecDeleter) SetVectorItem(index int, value any) (any, error) {
61+
if index == w.Magic {
62+
w.Value = value
63+
return w, nil
64+
}
65+
66+
return nil, fmt.Errorf("index %d out of bounds", index)
67+
}
68+
69+
func (w *customVecDeleter) DeleteVectorItem(index int) (any, error) {
70+
if index == w.Magic {
71+
w.Value = nil
72+
return w, nil
73+
}
74+
75+
return nil, fmt.Errorf("index %d out of bounds", index)
76+
}
77+
1278
func TestDelete(t *testing.T) {
1379
testcases := []struct {
1480
name string
@@ -125,6 +191,70 @@ func TestDelete(t *testing.T) {
125191
path: Path{3, "list"},
126192
invalid: true,
127193
},
194+
195+
// custom types
196+
197+
{
198+
name: "can delete in custom objects",
199+
dest: &customObjDeleter{
200+
Value: "old",
201+
},
202+
path: Path{"value"},
203+
expected: &customObjDeleter{
204+
Value: nil,
205+
},
206+
},
207+
{
208+
name: "can delete in custom objects",
209+
dest: &customObjDeleter{
210+
Value: map[string]any{
211+
"foo": "bar",
212+
"hello": "world",
213+
"list": []any{1, 2, 3},
214+
},
215+
},
216+
path: Path{"value", "list", 1},
217+
expected: &customObjDeleter{
218+
Value: map[string]any{
219+
"foo": "bar",
220+
"hello": "world",
221+
"list": []any{1, 3},
222+
},
223+
},
224+
},
225+
226+
{
227+
name: "can delete in custom vectors",
228+
dest: &customVecDeleter{
229+
Magic: 7,
230+
Value: "old",
231+
},
232+
path: Path{7},
233+
expected: &customVecDeleter{
234+
Magic: 7,
235+
Value: nil,
236+
},
237+
},
238+
{
239+
name: "can delete in custom vectors",
240+
dest: &customVecDeleter{
241+
Magic: 7,
242+
Value: map[string]any{
243+
"foo": "bar",
244+
"hello": "world",
245+
"list": []any{1, 2, 3},
246+
},
247+
},
248+
path: Path{7, "list", 1},
249+
expected: &customVecDeleter{
250+
Magic: 7,
251+
Value: map[string]any{
252+
"foo": "bar",
253+
"hello": "world",
254+
"list": []any{1, 3},
255+
},
256+
},
257+
},
128258
}
129259

130260
for _, tc := range testcases {

0 commit comments

Comments
 (0)