diff --git a/conf/config.go b/conf/config.go index af0de8b61..e1d43627d 100644 --- a/conf/config.go +++ b/conf/config.go @@ -9,20 +9,29 @@ import ( ) type Config struct { - Env interface{} - MapEnv bool - Types TypesTable - Operators OperatorsTable - Expect reflect.Kind - Optimize bool - Strict bool - DefaultType reflect.Type - ConstExprFns map[string]reflect.Value - Visitors []ast.Visitor - err error + Env interface{} + Types TypesTable + MapEnv bool + DefaultType reflect.Type + Operators OperatorsTable + Expect reflect.Kind + Optimize bool + Strict bool + ConstFns map[string]reflect.Value + Visitors []ast.Visitor } func New(env interface{}) *Config { + c := &Config{ + Operators: make(map[string][]string), + ConstFns: make(map[string]reflect.Value), + Optimize: true, + } + c.WithEnv(env) + return c +} + +func (c *Config) WithEnv(env interface{}) { var mapEnv bool var mapValueType reflect.Type if _, ok := env.(map[string]interface{}); ok { @@ -33,58 +42,37 @@ func New(env interface{}) *Config { } } - return &Config{ - Env: env, - MapEnv: mapEnv, - Types: CreateTypesTable(env), - Operators: make(map[string][]string), - Optimize: true, - Strict: true, - DefaultType: mapValueType, - ConstExprFns: make(map[string]reflect.Value), - } + c.Env = env + c.Types = CreateTypesTable(env) + c.MapEnv = mapEnv + c.DefaultType = mapValueType + c.Strict = true } -// Check validates the compiler configuration. -func (c *Config) Check() error { - // Check that all functions that define operator overloading - // exist in environment and have correct signatures. - for op, fns := range c.Operators { - for _, fn := range fns { - fnType, ok := c.Types[fn] - if !ok || fnType.Type.Kind() != reflect.Func { - return fmt.Errorf("function %s for %s operator does not exist in environment", fn, op) - } - requiredNumIn := 2 - if fnType.Method { - requiredNumIn = 3 // As first argument of method is receiver. - } - if fnType.Type.NumIn() != requiredNumIn || fnType.Type.NumOut() != 1 { - return fmt.Errorf("function %s for %s operator does not have a correct signature", fn, op) - } +func (c *Config) Operator(operator string, fns ...string) { + c.Operators[operator] = append(c.Operators[operator], fns...) + for _, fn := range fns { + fnType, ok := c.Types[fn] + if !ok || fnType.Type.Kind() != reflect.Func { + panic(fmt.Errorf("function %s for %s operator does not exist in the environment", fn, operator)) } - } - - // Check that all ConstExprFns are functions. - for name, fn := range c.ConstExprFns { - if fn.Kind() != reflect.Func { - return fmt.Errorf("const expression %q must be a function", name) + requiredNumIn := 2 + if fnType.Method { + requiredNumIn = 3 // As first argument of method is receiver. + } + if fnType.Type.NumIn() != requiredNumIn || fnType.Type.NumOut() != 1 { + panic(fmt.Errorf("function %s for %s operator does not have a correct signature", fn, operator)) } } - - return c.err } func (c *Config) ConstExpr(name string) { if c.Env == nil { - c.Error(fmt.Errorf("no environment for const expression: %v", name)) - return + panic("no environment is specified for ConstExpr()") } - c.ConstExprFns[name] = reflect.ValueOf(runtime.Fetch(c.Env, name)) -} - -func (c *Config) Error(err error) { - if c.err == nil { - c.err = err + fn := reflect.ValueOf(runtime.Fetch(c.Env, name)) + if fn.Kind() != reflect.Func { + panic(fmt.Errorf("const expression %q must be a function", name)) } + c.ConstFns[name] = fn } diff --git a/expr.go b/expr.go index 977c2dcd5..bbc4b2696 100644 --- a/expr.go +++ b/expr.go @@ -48,23 +48,12 @@ func Eval(input string, env interface{}) (interface{}, error) { // Methods defined on this type will be available as functions. func Env(env interface{}) Option { return func(c *conf.Config) { - if _, ok := env.(map[string]interface{}); ok { - c.MapEnv = true - } else { - if reflect.ValueOf(env).Kind() == reflect.Map { - c.DefaultType = reflect.TypeOf(env).Elem() - } - } - c.Strict = true - c.Types = conf.CreateTypesTable(env) - c.Env = env + c.WithEnv(env) } } // AllowUndefinedVariables allows to use undefined variables inside expressions. // This can be used with expr.Env option to partially define a few variables. -// Note what this option is only works in map environment are used, otherwise -// runtime.fetch will panic as there is no way to get missing field zero value. func AllowUndefinedVariables() Option { return func(c *conf.Config) { c.Strict = false @@ -74,7 +63,7 @@ func AllowUndefinedVariables() Option { // Operator allows to replace a binary operator with a function. func Operator(operator string, fn ...string) Option { return func(c *conf.Config) { - c.Operators[operator] = append(c.Operators[operator], fn...) + c.Operator(operator, fn...) } } @@ -124,9 +113,9 @@ func Patch(visitor ast.Visitor) Option { // Compile parses and compiles given input expression to bytecode program. func Compile(input string, ops ...Option) (*vm.Program, error) { config := &conf.Config{ - Operators: make(map[string][]string), - ConstExprFns: make(map[string]reflect.Value), - Optimize: true, + Operators: make(map[string][]string), + ConstFns: make(map[string]reflect.Value), + Optimize: true, } for _, op := range ops { @@ -140,10 +129,6 @@ func Compile(input string, ops ...Option) (*vm.Program, error) { }) } - if err := config.Check(); err != nil { - return nil, err - } - tree, err := parser.Parse(input) if err != nil { return nil, err diff --git a/expr_test.go b/expr_test.go index 8359c1f2e..f41ef478c 100644 --- a/expr_test.go +++ b/expr_test.go @@ -1180,23 +1180,22 @@ func TestConstExpr_error_wrong_type(t *testing.T) { env := map[string]interface{}{ "divide": 0, } - - _, err := expr.Compile( - `1 + divide(1, 0)`, - expr.Env(env), - expr.ConstExpr("divide"), - ) - require.Error(t, err) - require.Equal(t, "const expression \"divide\" must be a function", err.Error()) + assert.Panics(t, func() { + _, _ = expr.Compile( + `1 + divide(1, 0)`, + expr.Env(env), + expr.ConstExpr("divide"), + ) + }) } func TestConstExpr_error_no_env(t *testing.T) { - _, err := expr.Compile( - `1 + divide(1, 0)`, - expr.ConstExpr("divide"), - ) - require.Error(t, err) - require.Equal(t, "no environment for const expression: divide", err.Error()) + assert.Panics(t, func() { + _, _ = expr.Compile( + `1 + divide(1, 0)`, + expr.ConstExpr("divide"), + ) + }) } var stringer = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() diff --git a/optimizer/optimizer.go b/optimizer/optimizer.go index 738348dc3..9c97496c8 100644 --- a/optimizer/optimizer.go +++ b/optimizer/optimizer.go @@ -17,10 +17,10 @@ func Optimize(node *Node, config *conf.Config) error { break } } - if config != nil && len(config.ConstExprFns) > 0 { + if config != nil && len(config.ConstFns) > 0 { for limit := 100; limit >= 0; limit-- { constExpr := &constExpr{ - fns: config.ConstExprFns, + fns: config.ConstFns, } Walk(node, constExpr) if constExpr.err != nil {