Add option to allow using undefined variables

This commit is contained in:
Anton Medvedev
2019-11-07 12:17:02 +03:00
parent 47dd3b73fb
commit 8564cbf597
4 changed files with 52 additions and 44 deletions
+5
View File
@@ -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))
}
+13 -1
View File
@@ -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
+27 -38
View File
@@ -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),
+7 -5
View File
@@ -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 {