package operator_test import ( "fmt" "testing" "time" "github.com/expr-lang/expr/internal/testify/assert" "github.com/expr-lang/expr/internal/testify/require" "github.com/expr-lang/expr" "github.com/expr-lang/expr/test/mock" ) func TestOperator_struct(t *testing.T) { env := mock.Env{ Time: time.Date(2017, time.October, 23, 18, 30, 0, 0, time.UTC), } code := `Time == "2017-10-23"` program, err := expr.Compile(code, expr.Env(mock.Env{}), expr.Operator("==", "TimeEqualString")) require.NoError(t, err) output, err := expr.Run(program, env) require.NoError(t, err) require.Equal(t, true, output) } func TestOperator_no_env(t *testing.T) { code := `Time == "2017-10-23"` require.Panics(t, func() { _, _ = expr.Compile(code, expr.Operator("==", "TimeEqualString")) }) } func TestOperator_interface(t *testing.T) { env := mock.Env{} code := `Foo == "Foo.String" && "Foo.String" == Foo && Time != Foo && Time == Time` program, err := expr.Compile( code, expr.Env(mock.Env{}), expr.Operator("==", "StringerStringEqual", "StringStringerEqual", "StringerStringerEqual"), expr.Operator("!=", "NotStringerStringEqual", "NotStringStringerEqual", "NotStringerStringerEqual"), ) require.NoError(t, err) output, err := expr.Run(program, env) require.NoError(t, err) require.Equal(t, true, output) } type Value struct { Int int } func TestOperator_Function(t *testing.T) { env := map[string]interface{}{ "foo": Value{1}, "bar": Value{2}, } tests := []struct { input string want int }{ { input: `foo + bar`, want: 3, }, { input: `2 + 4`, want: 6, }, } for _, tt := range tests { t.Run(fmt.Sprintf(`operator function helper test %s`, tt.input), func(t *testing.T) { program, err := expr.Compile( tt.input, expr.Env(env), expr.Operator("+", "Add", "AddInt"), expr.Function("Add", func(args ...interface{}) (interface{}, error) { return args[0].(Value).Int + args[1].(Value).Int, nil }, new(func(_ Value, __ Value) int), ), expr.Function("AddInt", func(args ...interface{}) (interface{}, error) { return args[0].(int) + args[1].(int), nil }, new(func(_ int, __ int) int), ), ) require.NoError(t, err) output, err := expr.Run(program, env) require.NoError(t, err) require.Equal(t, tt.want, output) }) } } func TestOperator_Function_WithTypes(t *testing.T) { env := map[string]interface{}{ "foo": Value{1}, "bar": Value{2}, } assert.PanicsWithError(t, `function "Add" for "+" operator misses types`, func() { _, _ = expr.Compile( `foo + bar`, expr.Env(env), expr.Operator("+", "Add", "AddInt"), expr.Function("Add", func(args ...interface{}) (interface{}, error) { return args[0].(Value).Int + args[1].(Value).Int, nil }), ) }) assert.PanicsWithError(t, `function "Add" for "+" operator does not have a correct signature`, func() { _, _ = expr.Compile( `foo + bar`, expr.Env(env), expr.Operator("+", "Add", "AddInt"), expr.Function("Add", func(args ...interface{}) (interface{}, error) { return args[0].(Value).Int + args[1].(Value).Int, nil }, new(func(_ Value) int), ), ) }) } func TestOperator_FunctionOverTypesPrecedence(t *testing.T) { env := struct { Add func(a, b int) int }{ Add: func(a, b int) int { return a + b }, } program, err := expr.Compile( `1 + 2`, expr.Env(env), expr.Operator("+", "Add"), expr.Function("Add", func(args ...interface{}) (interface{}, error) { // Weird function that returns 100 + a + b for testing purposes. return args[0].(int) + args[1].(int) + 100, nil }, new(func(_ int, __ int) int), ), ) require.NoError(t, err) output, err := expr.Run(program, env) require.NoError(t, err) require.Equal(t, 103, output) } func TestOperator_CanBeDefinedEitherInTypesOrInFunctions(t *testing.T) { env := struct { Add func(a, b int) int }{ Add: func(a, b int) int { return a + b }, } program, err := expr.Compile( `1 + 2`, expr.Env(env), expr.Operator("+", "Add", "AddValues"), expr.Function("AddValues", func(args ...interface{}) (interface{}, error) { return args[0].(Value).Int + args[1].(Value).Int, nil }, new(func(_ Value, __ Value) int), ), ) require.NoError(t, err) output, err := expr.Run(program, env) require.NoError(t, err) require.Equal(t, 3, output) } func TestOperator_Polymorphic(t *testing.T) { env := struct { Add func(a, b int) int Foo Value Bar Value }{ Add: func(a, b int) int { return a + b }, Foo: Value{1}, Bar: Value{2}, } program, err := expr.Compile( `1 + 2 + (Foo + Bar)`, expr.Env(env), expr.Operator("+", "Add", "AddValues"), expr.Function("AddValues", func(args ...interface{}) (interface{}, error) { return args[0].(Value).Int + args[1].(Value).Int, nil }, new(func(_ Value, __ Value) int), ), ) require.NoError(t, err) output, err := expr.Run(program, env) require.NoError(t, err) require.Equal(t, 6, output) } func TestOperator_recursive_apply(t *testing.T) { type Decimal struct { Int int } env := map[string]any{ "add": func(a, b Decimal) Decimal { return Decimal{ Int: a.Int + b.Int, } }, "addInt": func(a Decimal, b int) Decimal { return Decimal{ Int: a.Int + b, } }, "a": Decimal{1}, "b": Decimal{2}, "c": Decimal{3}, "d": Decimal{4}, "e": Decimal{5}, } program, err := expr.Compile( `a + b + 100 + c + d + e`, expr.Env(env), expr.Operator("+", "add"), expr.Operator("+", "addInt"), ) require.NoError(t, err) require.Equal(t, `add(add(add(addInt(add(a, b), 100), c), d), e)`, program.Node().String()) output, err := expr.Run(program, env) require.NoError(t, err) require.Equal(t, 115, output.(Decimal).Int) }