Skip to content

Commit b9f96a0

Browse files
authored
Add concat() builtin (#565)
* Add concat() builtin * Use pkg deref in builtin * Use pkg deref in checker * Use pkg deref in types_table.go * Use pkg deref in docgen.go * Use pkg deref in vm
1 parent 36f9adb commit b9f96a0

File tree

13 files changed

+255
-149
lines changed

13 files changed

+255
-149
lines changed

builtin/builtin.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strings"
1010
"time"
1111

12+
"github.com/expr-lang/expr/internal/deref"
1213
"github.com/expr-lang/expr/vm/runtime"
1314
)
1415

@@ -440,7 +441,7 @@ var Builtins = []*Function{
440441
sum := int64(0)
441442
i := 0
442443
for ; i < v.Len(); i++ {
443-
it := deref(v.Index(i))
444+
it := deref.Value(v.Index(i))
444445
if it.CanInt() {
445446
sum += it.Int()
446447
} else if it.CanFloat() {
@@ -453,7 +454,7 @@ var Builtins = []*Function{
453454
float:
454455
fSum := float64(sum)
455456
for ; i < v.Len(); i++ {
456-
it := deref(v.Index(i))
457+
it := deref.Value(v.Index(i))
457458
if it.CanInt() {
458459
fSum += float64(it.Int())
459460
} else if it.CanFloat() {
@@ -492,7 +493,7 @@ var Builtins = []*Function{
492493
sum := float64(0)
493494
i := 0
494495
for ; i < v.Len(); i++ {
495-
it := deref(v.Index(i))
496+
it := deref.Value(v.Index(i))
496497
if it.CanInt() {
497498
sum += float64(it.Int())
498499
} else if it.CanFloat() {
@@ -530,7 +531,7 @@ var Builtins = []*Function{
530531
}
531532
s := make([]float64, v.Len())
532533
for i := 0; i < v.Len(); i++ {
533-
it := deref(v.Index(i))
534+
it := deref.Value(v.Index(i))
534535
if it.CanInt() {
535536
s[i] = float64(it.Int())
536537
} else if it.CanFloat() {
@@ -850,7 +851,7 @@ var Builtins = []*Function{
850851
}
851852
out := reflect.MakeMap(mapType)
852853
for i := 0; i < v.Len(); i++ {
853-
pair := deref(v.Index(i))
854+
pair := deref.Value(v.Index(i))
854855
if pair.Kind() != reflect.Array && pair.Kind() != reflect.Slice {
855856
return nil, fmt.Errorf("invalid pair %v", pair)
856857
}
@@ -908,6 +909,49 @@ var Builtins = []*Function{
908909
}
909910
},
910911
},
912+
{
913+
Name: "concat",
914+
Safe: func(args ...any) (any, uint, error) {
915+
if len(args) == 0 {
916+
return nil, 0, fmt.Errorf("invalid number of arguments (expected at least 1, got 0)")
917+
}
918+
919+
var size uint
920+
var arr []any
921+
922+
for _, arg := range args {
923+
v := reflect.ValueOf(deref.Deref(arg))
924+
925+
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
926+
return nil, 0, fmt.Errorf("cannot concat %s", v.Kind())
927+
}
928+
929+
size += uint(v.Len())
930+
931+
for i := 0; i < v.Len(); i++ {
932+
item := v.Index(i)
933+
arr = append(arr, item.Interface())
934+
}
935+
}
936+
937+
return arr, size, nil
938+
},
939+
Validate: func(args []reflect.Type) (reflect.Type, error) {
940+
if len(args) == 0 {
941+
return anyType, fmt.Errorf("invalid number of arguments (expected at least 1, got 0)")
942+
}
943+
944+
for _, arg := range args {
945+
switch kind(deref.Type(arg)) {
946+
case reflect.Interface, reflect.Slice, reflect.Array:
947+
default:
948+
return anyType, fmt.Errorf("cannot concat %s", arg)
949+
}
950+
}
951+
952+
return arrayType, nil
953+
},
954+
},
911955
{
912956
Name: "sort",
913957
Safe: func(args ...any) (any, uint, error) {

builtin/builtin_test.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ import (
1919
)
2020

2121
func TestBuiltin(t *testing.T) {
22+
ArrayWithNil := []any{42}
2223
env := map[string]any{
23-
"ArrayOfString": []string{"foo", "bar", "baz"},
24-
"ArrayOfInt": []int{1, 2, 3},
25-
"ArrayOfAny": []any{1, "2", true},
26-
"ArrayOfFoo": []mock.Foo{{Value: "a"}, {Value: "b"}, {Value: "c"}},
24+
"ArrayOfString": []string{"foo", "bar", "baz"},
25+
"ArrayOfInt": []int{1, 2, 3},
26+
"ArrayOfAny": []any{1, "2", true},
27+
"ArrayOfFoo": []mock.Foo{{Value: "a"}, {Value: "b"}, {Value: "c"}},
28+
"PtrArrayWithNil": &ArrayWithNil,
2729
}
2830

2931
var tests = []struct {
@@ -130,6 +132,8 @@ func TestBuiltin(t *testing.T) {
130132
{`reduce(1..9, # + #acc)`, 45},
131133
{`reduce([.5, 1.5, 2.5], # + #acc, 0)`, 4.5},
132134
{`reduce([], 5, 0)`, 0},
135+
{`concat(ArrayOfString, ArrayOfInt)`, []any{"foo", "bar", "baz", 1, 2, 3}},
136+
{`concat(PtrArrayWithNil, [nil])`, []any{42, nil}},
133137
}
134138

135139
for _, test := range tests {

builtin/utils.go

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,35 +35,6 @@ func types(types ...any) []reflect.Type {
3535
return ts
3636
}
3737

38-
func deref(v reflect.Value) reflect.Value {
39-
if v.Kind() == reflect.Interface {
40-
if v.IsNil() {
41-
return v
42-
}
43-
v = v.Elem()
44-
}
45-
46-
loop:
47-
for v.Kind() == reflect.Ptr {
48-
if v.IsNil() {
49-
return v
50-
}
51-
indirect := reflect.Indirect(v)
52-
switch indirect.Kind() {
53-
case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
54-
break loop
55-
default:
56-
v = v.Elem()
57-
}
58-
}
59-
60-
if v.IsValid() {
61-
return v
62-
}
63-
64-
panic(fmt.Sprintf("cannot deref %s", v))
65-
}
66-
6738
func toInt(val any) (int, error) {
6839
switch v := val.(type) {
6940
case int:

checker/checker.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/expr-lang/expr/builtin"
1010
"github.com/expr-lang/expr/conf"
1111
"github.com/expr-lang/expr/file"
12+
"github.com/expr-lang/expr/internal/deref"
1213
"github.com/expr-lang/expr/parser"
1314
)
1415

@@ -203,8 +204,7 @@ func (v *checker) ConstantNode(node *ast.ConstantNode) (reflect.Type, info) {
203204

204205
func (v *checker) UnaryNode(node *ast.UnaryNode) (reflect.Type, info) {
205206
t, _ := v.visit(node.Node)
206-
207-
t = deref(t)
207+
t = deref.Type(t)
208208

209209
switch node.Operator {
210210

@@ -235,8 +235,8 @@ func (v *checker) BinaryNode(node *ast.BinaryNode) (reflect.Type, info) {
235235
l, _ := v.visit(node.Left)
236236
r, ri := v.visit(node.Right)
237237

238-
l = deref(l)
239-
r = deref(r)
238+
l = deref.Type(l)
239+
r = deref.Type(r)
240240

241241
// check operator overloading
242242
if fns, ok := v.config.Operators[node.Operator]; ok {

checker/types.go

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -205,25 +205,6 @@ func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
205205
return reflect.StructField{}, false
206206
}
207207

208-
func deref(t reflect.Type) reflect.Type {
209-
if t == nil {
210-
return nil
211-
}
212-
if t.Kind() == reflect.Interface {
213-
return t
214-
}
215-
for t != nil && t.Kind() == reflect.Ptr {
216-
e := t.Elem()
217-
switch e.Kind() {
218-
case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
219-
return t
220-
default:
221-
t = e
222-
}
223-
}
224-
return t
225-
}
226-
227208
func kind(t reflect.Type) reflect.Kind {
228209
if t == nil {
229210
return reflect.Invalid

conf/types_table.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package conf
22

33
import (
44
"reflect"
5+
6+
"github.com/expr-lang/expr/internal/deref"
57
)
68

79
type Tag struct {
@@ -77,7 +79,7 @@ func CreateTypesTable(i any) TypesTable {
7779

7880
func FieldsFromStruct(t reflect.Type) TypesTable {
7981
types := make(TypesTable)
80-
t = dereference(t)
82+
t = deref.Type(t)
8183
if t == nil {
8284
return types
8385
}
@@ -111,23 +113,6 @@ func FieldsFromStruct(t reflect.Type) TypesTable {
111113
return types
112114
}
113115

114-
func dereference(t reflect.Type) reflect.Type {
115-
if t == nil {
116-
return nil
117-
}
118-
if t.Kind() == reflect.Ptr {
119-
t = dereference(t.Elem())
120-
}
121-
return t
122-
}
123-
124-
func kind(t reflect.Type) reflect.Kind {
125-
if t == nil {
126-
return reflect.Invalid
127-
}
128-
return t.Kind()
129-
}
130-
131116
func FieldName(field reflect.StructField) string {
132117
if taggedName := field.Tag.Get("expr"); taggedName != "" {
133118
return taggedName

docgen/docgen.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/expr-lang/expr/conf"
9+
"github.com/expr-lang/expr/internal/deref"
910
)
1011

1112
// Kind can be any of array, map, struct, func, string, int, float, bool or any.
@@ -80,7 +81,7 @@ func CreateDoc(i any) *Context {
8081
c := &Context{
8182
Variables: make(map[Identifier]*Type),
8283
Types: make(map[TypeName]*Type),
83-
PkgPath: dereference(reflect.TypeOf(i)).PkgPath(),
84+
PkgPath: deref.Type(reflect.TypeOf(i)).PkgPath(),
8485
}
8586

8687
for name, t := range conf.CreateTypesTable(i) {
@@ -134,7 +135,7 @@ func (c *Context) use(t reflect.Type, ops ...option) *Type {
134135
methods = append(methods, m)
135136
}
136137

137-
t = dereference(t)
138+
t = deref.Type(t)
138139

139140
// Only named types will have methods defined on them.
140141
// It maybe not even struct, but we gonna call then
@@ -253,13 +254,3 @@ func isPrivate(s string) bool {
253254
func isProtobuf(s string) bool {
254255
return strings.HasPrefix(s, "XXX_")
255256
}
256-
257-
func dereference(t reflect.Type) reflect.Type {
258-
if t == nil {
259-
return nil
260-
}
261-
if t.Kind() == reflect.Ptr {
262-
t = dereference(t.Elem())
263-
}
264-
return t
265-
}

expr_test.go

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -316,9 +316,9 @@ func ExampleOperator_Decimal() {
316316
code := `A + B - C`
317317

318318
type Env struct {
319-
A, B, C Decimal
320-
Sub func(a, b Decimal) Decimal
321-
Add func(a, b Decimal) Decimal
319+
A, B, C Decimal
320+
Sub func(a, b Decimal) Decimal
321+
Add func(a, b Decimal) Decimal
322322
}
323323

324324
options := []expr.Option{
@@ -334,11 +334,11 @@ func ExampleOperator_Decimal() {
334334
}
335335

336336
env := Env{
337-
A: Decimal{3},
338-
B: Decimal{2},
339-
C: Decimal{1},
337+
A: Decimal{3},
338+
B: Decimal{2},
339+
C: Decimal{1},
340340
Sub: func(a, b Decimal) Decimal { return Decimal{a.N - b.N} },
341-
Add: func(a, b Decimal) Decimal { return Decimal{a.N + b.N} },
341+
Add: func(a, b Decimal) Decimal { return Decimal{a.N + b.N} },
342342
}
343343

344344
output, err := expr.Run(program, env)
@@ -1358,21 +1358,6 @@ func TestExpr_fetch_from_func(t *testing.T) {
13581358
assert.Contains(t, err.Error(), "cannot fetch Value from func()")
13591359
}
13601360

1361-
func TestExpr_fetch_from_interface(t *testing.T) {
1362-
type FooBar struct {
1363-
Value string
1364-
}
1365-
foobar := &FooBar{"waldo"}
1366-
var foobarAny any = foobar
1367-
var foobarPtrAny any = &foobarAny
1368-
1369-
res, err := expr.Eval("foo.Value", map[string]any{
1370-
"foo": foobarPtrAny,
1371-
})
1372-
assert.NoError(t, err)
1373-
assert.Equal(t, "waldo", res)
1374-
}
1375-
13761361
func TestExpr_map_default_values(t *testing.T) {
13771362
env := map[string]any{
13781363
"foo": map[string]string{},

0 commit comments

Comments
 (0)