Skip to content

Commit

Permalink
feat: Operator overload from Function
Browse files Browse the repository at this point in the history
  • Loading branch information
nikolaymatrosov committed Aug 17, 2023
1 parent 47eae7e commit 1c7ad66
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 5 deletions.
8 changes: 7 additions & 1 deletion conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func CreateNew() *Config {
Operators: make(map[string][]string),
ConstFns: make(map[string]reflect.Value),
Functions: make(map[string]*builtin.Function),
Types: make(TypesTable),
Optimize: true,
}
for _, f := range builtin.Functions {
Expand Down Expand Up @@ -87,10 +88,15 @@ func (c *Config) Check() {
panic(fmt.Errorf("function %s for %s operator does not exist in the environment", fn, operator))
}
requiredNumIn := 2
requiredNumOut := 1
if fnType.Method {
requiredNumIn = 3 // As first argument of method is receiver.
}
if fnType.Type.NumIn() != requiredNumIn || fnType.Type.NumOut() != 1 {
if fnType.Type.NumIn() == 1 && fnType.Type.In(0).Kind() == reflect.Slice {
requiredNumIn = 1
requiredNumOut = 2
}
if fnType.Type.NumIn() != requiredNumIn || fnType.Type.NumOut() != requiredNumOut {
panic(fmt.Errorf("function %s for %s operator does not have a correct signature", fn, operator))
}
}
Expand Down
12 changes: 10 additions & 2 deletions conf/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ func FindSuitableOperatorOverload(fns []string, types TypesTable, l, r reflect.T
if fnType.Method {
firstInIndex = 1 // As first argument to method is receiver.
}
firstArgType := fnType.Type.In(firstInIndex)
secondArgType := fnType.Type.In(firstInIndex + 1)
var firstArgType reflect.Type
var secondArgType reflect.Type

if fnType.Type.NumIn() == 1 && fnType.Type.In(0).Kind() == reflect.Slice {
firstArgType = fnType.Type.In(0).Elem()
secondArgType = fnType.Type.In(0).Elem()
} else {
firstArgType = fnType.Type.In(firstInIndex)
secondArgType = fnType.Type.In(firstInIndex + 1)
}

firstArgumentFit := l == firstArgType || (firstArgType.Kind() == reflect.Interface && (l == nil || l.Implements(firstArgType)))
secondArgumentFit := r == secondArgType || (secondArgType.Kind() == reflect.Interface && (r == nil || r.Implements(secondArgType)))
Expand Down
4 changes: 2 additions & 2 deletions conf/types_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ type TypesTable map[string]Tag
// If map is passed, all items will be treated as variables
// (key as name, value as type).
func CreateTypesTable(i interface{}) TypesTable {
types := make(TypesTable)
if i == nil {
return nil
return types
}

types := make(TypesTable)
v := reflect.ValueOf(i)
t := reflect.TypeOf(i)

Expand Down
3 changes: 3 additions & 0 deletions expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ func Function(name string, fn func(params ...interface{}) (interface{}, error),
Func: fn,
Types: ts,
}
c.Types[name] = conf.Tag{
Type: reflect.TypeOf(fn),
}
}
}

Expand Down
25 changes: 25 additions & 0 deletions test/operator/operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,28 @@ func TestOperator_interface(t *testing.T) {
require.NoError(t, err)
require.Equal(t, true, output)
}

func TestOperator_Function(t *testing.T) {
env := map[string]interface{}{
"foo": Value{1},
"bar": Value{2},
}

program, err := expr.Compile(
`foo + bar`,
expr.Env(env),
expr.Operator("+", "Add"),
expr.Function("Add", func(args ...interface{}) (interface{}, error) {
return args[0].(Value).Int + args[1].(Value).Int, nil
}),
)
require.NoError(t, err)

output, err := expr.Run(program, env)
require.NoError(t, err)
require.Equal(t, 3, output)
}

type Value struct {
Int int
}

0 comments on commit 1c7ad66

Please sign in to comment.