diff --git a/checker/checker.go b/checker/checker.go index 3e320621d..bd9a6d4a7 100644 --- a/checker/checker.go +++ b/checker/checker.go @@ -28,6 +28,7 @@ func Check(tree *parser.Tree, config *conf.Config) (t reflect.Type, err error) { v.types = config.Types v.operators = config.Operators v.expect = config.Expect + v.undefVars = config.AllowUndefinedVariables } t = v.visit(tree.Node) @@ -55,6 +56,7 @@ type visitor struct { operators conf.OperatorsTable expect reflect.Kind collections []reflect.Type + undefVars bool } func (v *visitor) visit(node ast.Node) reflect.Type { @@ -127,6 +129,9 @@ func (v *visitor) IdentifierNode(node *ast.IdentifierNode) reflect.Type { if t, ok := v.types[node.Value]; ok { return t.Type } + if v.undefVars { + return interfaceType + } panic(v.error(node, "unknown name %v", node.Value)) } diff --git a/expr.go b/expr.go index d65a06f50..191e173d0 100644 --- a/expr.go +++ b/expr.go @@ -49,10 +49,22 @@ func Env(i interface{}) Option { if _, ok := i.(map[string]interface{}); ok { c.MapEnv = true } + c.CheckTypes = true c.Types = conf.CreateTypesTable(i) } } +// 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.CheckTypes = true + c.AllowUndefinedVariables = true + } +} + // Operator allows to override binary operator with function. func Operator(operator string, fn ...string) Option { return func(c *conf.Config) { @@ -108,7 +120,7 @@ func Compile(input string, ops ...Option) (*vm.Program, error) { return nil, err } - if config.Types != nil { + if config.CheckTypes { _, err = checker.Check(tree, config) if err != nil { return nil, err diff --git a/expr_test.go b/expr_test.go index 378c4ef0c..fbd42dd75 100644 --- a/expr_test.go +++ b/expr_test.go @@ -1,14 +1,12 @@ package expr_test import ( - "encoding/json" "fmt" "strings" "testing" "time" "github.com/antonmedv/expr" - "github.com/antonmedv/expr/vm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -209,6 +207,33 @@ func ExampleEnv_with_undefined_variables() { // Output: 5 } +func ExampleEnv_allow_undefined_variables() { + env := map[string]string{ + "greet": "", + } + + program, err := expr.Compile(`greet + name`, expr.Env(env), expr.AllowUndefinedVariables()) + if err != nil { + fmt.Printf("%v", err) + return + } + + params := map[string]string{ + "greet": "hello, ", + "name": "world", + } + + output, err := expr.Run(program, params) + if err != nil { + fmt.Printf("%v", err) + return + } + + fmt.Printf("%v", output) + + // Output: hello, world +} + func ExampleAsBool() { env := map[string]int{ "foo": 0, @@ -359,42 +384,6 @@ func ExampleOperator_time() { // Output: true } -func ExampleEval_marshal() { - env := map[string]int{ - "foo": 1, - "bar": 2, - } - - program, err := expr.Compile("(foo + bar) in [1, 2, 3]", expr.Env(env)) - if err != nil { - fmt.Printf("%v", err) - return - } - - b, err := json.Marshal(program) - if err != nil { - fmt.Printf("%v", err) - return - } - - unmarshaledProgram := &vm.Program{} - err = json.Unmarshal(b, unmarshaledProgram) - if err != nil { - fmt.Printf("%v", err) - return - } - - output, err := expr.Run(unmarshaledProgram, env) - if err != nil { - fmt.Printf("%v", err) - return - } - - fmt.Printf("%v", output) - - // Output: true -} - func TestOperator_struct(t *testing.T) { env := &mockEnv{ BirthDay: time.Date(2017, time.October, 23, 18, 30, 0, 0, time.UTC), diff --git a/internal/conf/config.go b/internal/conf/config.go index d22972393..a0086e150 100644 --- a/internal/conf/config.go +++ b/internal/conf/config.go @@ -6,11 +6,13 @@ import ( ) type Config struct { - MapEnv bool - Types TypesTable - Operators OperatorsTable - Expect reflect.Kind - Optimize bool + MapEnv bool + Types TypesTable + CheckTypes bool + Operators OperatorsTable + Expect reflect.Kind + Optimize bool + AllowUndefinedVariables bool } func New(i interface{}) *Config {