Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/expr-lang/expr/internal/deref"
"github.com/expr-lang/expr/vm/runtime"
)

Expand Down Expand Up @@ -440,7 +441,7 @@ var Builtins = []*Function{
sum := int64(0)
i := 0
for ; i < v.Len(); i++ {
it := deref(v.Index(i))
it := deref.Value(v.Index(i))
if it.CanInt() {
sum += it.Int()
} else if it.CanFloat() {
Expand All @@ -453,7 +454,7 @@ var Builtins = []*Function{
float:
fSum := float64(sum)
for ; i < v.Len(); i++ {
it := deref(v.Index(i))
it := deref.Value(v.Index(i))
if it.CanInt() {
fSum += float64(it.Int())
} else if it.CanFloat() {
Expand Down Expand Up @@ -492,7 +493,7 @@ var Builtins = []*Function{
sum := float64(0)
i := 0
for ; i < v.Len(); i++ {
it := deref(v.Index(i))
it := deref.Value(v.Index(i))
if it.CanInt() {
sum += float64(it.Int())
} else if it.CanFloat() {
Expand Down Expand Up @@ -530,7 +531,7 @@ var Builtins = []*Function{
}
s := make([]float64, v.Len())
for i := 0; i < v.Len(); i++ {
it := deref(v.Index(i))
it := deref.Value(v.Index(i))
if it.CanInt() {
s[i] = float64(it.Int())
} else if it.CanFloat() {
Expand Down Expand Up @@ -850,7 +851,7 @@ var Builtins = []*Function{
}
out := reflect.MakeMap(mapType)
for i := 0; i < v.Len(); i++ {
pair := deref(v.Index(i))
pair := deref.Value(v.Index(i))
if pair.Kind() != reflect.Array && pair.Kind() != reflect.Slice {
return nil, fmt.Errorf("invalid pair %v", pair)
}
Expand Down Expand Up @@ -908,6 +909,49 @@ var Builtins = []*Function{
}
},
},
{
Name: "concat",
Safe: func(args ...any) (any, uint, error) {
if len(args) == 0 {
return nil, 0, fmt.Errorf("invalid number of arguments (expected at least 1, got 0)")
}

var size uint
var arr []any

for _, arg := range args {
v := reflect.ValueOf(deref.Deref(arg))

if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
return nil, 0, fmt.Errorf("cannot concat %s", v.Kind())
}

size += uint(v.Len())

for i := 0; i < v.Len(); i++ {
item := v.Index(i)
arr = append(arr, item.Interface())
}
}

return arr, size, nil
},
Validate: func(args []reflect.Type) (reflect.Type, error) {
if len(args) == 0 {
return anyType, fmt.Errorf("invalid number of arguments (expected at least 1, got 0)")
}

for _, arg := range args {
switch kind(deref.Type(arg)) {
case reflect.Interface, reflect.Slice, reflect.Array:
default:
return anyType, fmt.Errorf("cannot concat %s", arg)
}
}

return arrayType, nil
},
},
{
Name: "sort",
Safe: func(args ...any) (any, uint, error) {
Expand Down
12 changes: 8 additions & 4 deletions builtin/builtin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import (
)

func TestBuiltin(t *testing.T) {
ArrayWithNil := []any{42}
env := map[string]any{
"ArrayOfString": []string{"foo", "bar", "baz"},
"ArrayOfInt": []int{1, 2, 3},
"ArrayOfAny": []any{1, "2", true},
"ArrayOfFoo": []mock.Foo{{Value: "a"}, {Value: "b"}, {Value: "c"}},
"ArrayOfString": []string{"foo", "bar", "baz"},
"ArrayOfInt": []int{1, 2, 3},
"ArrayOfAny": []any{1, "2", true},
"ArrayOfFoo": []mock.Foo{{Value: "a"}, {Value: "b"}, {Value: "c"}},
"PtrArrayWithNil": &ArrayWithNil,
}

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

for _, test := range tests {
Expand Down
29 changes: 0 additions & 29 deletions builtin/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,6 @@ func types(types ...any) []reflect.Type {
return ts
}

func deref(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
if v.IsNil() {
return v
}
v = v.Elem()
}

loop:
for v.Kind() == reflect.Ptr {
if v.IsNil() {
return v
}
indirect := reflect.Indirect(v)
switch indirect.Kind() {
case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
break loop
default:
v = v.Elem()
}
}

if v.IsValid() {
return v
}

panic(fmt.Sprintf("cannot deref %s", v))
}

func toInt(val any) (int, error) {
switch v := val.(type) {
case int:
Expand Down
8 changes: 4 additions & 4 deletions checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/conf"
"github.com/expr-lang/expr/file"
"github.com/expr-lang/expr/internal/deref"
"github.com/expr-lang/expr/parser"
)

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

func (v *checker) UnaryNode(node *ast.UnaryNode) (reflect.Type, info) {
t, _ := v.visit(node.Node)

t = deref(t)
t = deref.Type(t)

switch node.Operator {

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

l = deref(l)
r = deref(r)
l = deref.Type(l)
r = deref.Type(r)

// check operator overloading
if fns, ok := v.config.Operators[node.Operator]; ok {
Expand Down
19 changes: 0 additions & 19 deletions checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,25 +205,6 @@ func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
return reflect.StructField{}, false
}

func deref(t reflect.Type) reflect.Type {
if t == nil {
return nil
}
if t.Kind() == reflect.Interface {
return t
}
for t != nil && t.Kind() == reflect.Ptr {
e := t.Elem()
switch e.Kind() {
case reflect.Struct, reflect.Map, reflect.Array, reflect.Slice:
return t
default:
t = e
}
}
return t
}

func kind(t reflect.Type) reflect.Kind {
if t == nil {
return reflect.Invalid
Expand Down
21 changes: 3 additions & 18 deletions conf/types_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package conf

import (
"reflect"

"github.com/expr-lang/expr/internal/deref"
)

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

func FieldsFromStruct(t reflect.Type) TypesTable {
types := make(TypesTable)
t = dereference(t)
t = deref.Type(t)
if t == nil {
return types
}
Expand Down Expand Up @@ -111,23 +113,6 @@ func FieldsFromStruct(t reflect.Type) TypesTable {
return types
}

func dereference(t reflect.Type) reflect.Type {
if t == nil {
return nil
}
if t.Kind() == reflect.Ptr {
t = dereference(t.Elem())
}
return t
}

func kind(t reflect.Type) reflect.Kind {
if t == nil {
return reflect.Invalid
}
return t.Kind()
}

func FieldName(field reflect.StructField) string {
if taggedName := field.Tag.Get("expr"); taggedName != "" {
return taggedName
Expand Down
15 changes: 3 additions & 12 deletions docgen/docgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/expr-lang/expr/conf"
"github.com/expr-lang/expr/internal/deref"
)

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

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

t = dereference(t)
t = deref.Type(t)

// Only named types will have methods defined on them.
// It maybe not even struct, but we gonna call then
Expand Down Expand Up @@ -253,13 +254,3 @@ func isPrivate(s string) bool {
func isProtobuf(s string) bool {
return strings.HasPrefix(s, "XXX_")
}

func dereference(t reflect.Type) reflect.Type {
if t == nil {
return nil
}
if t.Kind() == reflect.Ptr {
t = dereference(t.Elem())
}
return t
}
29 changes: 7 additions & 22 deletions expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,9 @@ func ExampleOperator_Decimal() {
code := `A + B - C`

type Env struct {
A, B, C Decimal
Sub func(a, b Decimal) Decimal
Add func(a, b Decimal) Decimal
A, B, C Decimal
Sub func(a, b Decimal) Decimal
Add func(a, b Decimal) Decimal
}

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

env := Env{
A: Decimal{3},
B: Decimal{2},
C: Decimal{1},
A: Decimal{3},
B: Decimal{2},
C: Decimal{1},
Sub: func(a, b Decimal) Decimal { return Decimal{a.N - b.N} },
Add: func(a, b Decimal) Decimal { return Decimal{a.N + b.N} },
Add: func(a, b Decimal) Decimal { return Decimal{a.N + b.N} },
}

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

func TestExpr_fetch_from_interface(t *testing.T) {
type FooBar struct {
Value string
}
foobar := &FooBar{"waldo"}
var foobarAny any = foobar
var foobarPtrAny any = &foobarAny

res, err := expr.Eval("foo.Value", map[string]any{
"foo": foobarPtrAny,
})
assert.NoError(t, err)
assert.Equal(t, "waldo", res)
}

func TestExpr_map_default_values(t *testing.T) {
env := map[string]any{
"foo": map[string]string{},
Expand Down
Loading