mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Move code duplicate to conf package
This commit is contained in:
+43
-55
@@ -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))
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+13
-14
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user