Skip to content

Commit c9f3ed9

Browse files
committed
Add option to allow using undefined variables
1 parent 1042ff4 commit c9f3ed9

File tree

4 files changed

+52
-44
lines changed

4 files changed

+52
-44
lines changed

checker/checker.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func Check(tree *parser.Tree, config *conf.Config) (t reflect.Type, err error) {
2828
v.types = config.Types
2929
v.operators = config.Operators
3030
v.expect = config.Expect
31+
v.undefVars = config.AllowUndefinedVariables
3132
}
3233

3334
t = v.visit(tree.Node)
@@ -55,6 +56,7 @@ type visitor struct {
5556
operators conf.OperatorsTable
5657
expect reflect.Kind
5758
collections []reflect.Type
59+
undefVars bool
5860
}
5961

6062
func (v *visitor) visit(node ast.Node) reflect.Type {
@@ -127,6 +129,9 @@ func (v *visitor) IdentifierNode(node *ast.IdentifierNode) reflect.Type {
127129
if t, ok := v.types[node.Value]; ok {
128130
return t.Type
129131
}
132+
if v.undefVars {
133+
return interfaceType
134+
}
130135
panic(v.error(node, "unknown name %v", node.Value))
131136
}
132137

expr.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,22 @@ func Env(i interface{}) Option {
4949
if _, ok := i.(map[string]interface{}); ok {
5050
c.MapEnv = true
5151
}
52+
c.CheckTypes = true
5253
c.Types = conf.CreateTypesTable(i)
5354
}
5455
}
5556

57+
// AllowUndefinedVariables allows to use undefined variables inside expressions.
58+
// This can be used with expr.Env option to partially define a few variables.
59+
// Note what this option is only works in map environment are used, otherwise
60+
// runtime.fetch will panic as there is no way to get missing field zero value.
61+
func AllowUndefinedVariables() Option {
62+
return func(c *conf.Config) {
63+
c.CheckTypes = true
64+
c.AllowUndefinedVariables = true
65+
}
66+
}
67+
5668
// Operator allows to override binary operator with function.
5769
func Operator(operator string, fn ...string) Option {
5870
return func(c *conf.Config) {
@@ -108,7 +120,7 @@ func Compile(input string, ops ...Option) (*vm.Program, error) {
108120
return nil, err
109121
}
110122

111-
if config.Types != nil {
123+
if config.CheckTypes {
112124
_, err = checker.Check(tree, config)
113125
if err != nil {
114126
return nil, err

expr_test.go

Lines changed: 27 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
package expr_test
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"strings"
76
"testing"
87
"time"
98

109
"github.com/antonmedv/expr"
11-
"github.com/antonmedv/expr/vm"
1210
"github.com/stretchr/testify/assert"
1311
"github.com/stretchr/testify/require"
1412
)
@@ -209,6 +207,33 @@ func ExampleEnv_with_undefined_variables() {
209207
// Output: 5
210208
}
211209

210+
func ExampleEnv_allow_undefined_variables() {
211+
env := map[string]string{
212+
"greet": "",
213+
}
214+
215+
program, err := expr.Compile(`greet + name`, expr.Env(env), expr.AllowUndefinedVariables())
216+
if err != nil {
217+
fmt.Printf("%v", err)
218+
return
219+
}
220+
221+
params := map[string]string{
222+
"greet": "hello, ",
223+
"name": "world",
224+
}
225+
226+
output, err := expr.Run(program, params)
227+
if err != nil {
228+
fmt.Printf("%v", err)
229+
return
230+
}
231+
232+
fmt.Printf("%v", output)
233+
234+
// Output: hello, world
235+
}
236+
212237
func ExampleAsBool() {
213238
env := map[string]int{
214239
"foo": 0,
@@ -359,42 +384,6 @@ func ExampleOperator_time() {
359384
// Output: true
360385
}
361386

362-
func ExampleEval_marshal() {
363-
env := map[string]int{
364-
"foo": 1,
365-
"bar": 2,
366-
}
367-
368-
program, err := expr.Compile("(foo + bar) in [1, 2, 3]", expr.Env(env))
369-
if err != nil {
370-
fmt.Printf("%v", err)
371-
return
372-
}
373-
374-
b, err := json.Marshal(program)
375-
if err != nil {
376-
fmt.Printf("%v", err)
377-
return
378-
}
379-
380-
unmarshaledProgram := &vm.Program{}
381-
err = json.Unmarshal(b, unmarshaledProgram)
382-
if err != nil {
383-
fmt.Printf("%v", err)
384-
return
385-
}
386-
387-
output, err := expr.Run(unmarshaledProgram, env)
388-
if err != nil {
389-
fmt.Printf("%v", err)
390-
return
391-
}
392-
393-
fmt.Printf("%v", output)
394-
395-
// Output: true
396-
}
397-
398387
func TestOperator_struct(t *testing.T) {
399388
env := &mockEnv{
400389
BirthDay: time.Date(2017, time.October, 23, 18, 30, 0, 0, time.UTC),

internal/conf/config.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import (
66
)
77

88
type Config struct {
9-
MapEnv bool
10-
Types TypesTable
11-
Operators OperatorsTable
12-
Expect reflect.Kind
13-
Optimize bool
9+
MapEnv bool
10+
Types TypesTable
11+
CheckTypes bool
12+
Operators OperatorsTable
13+
Expect reflect.Kind
14+
Optimize bool
15+
AllowUndefinedVariables bool
1416
}
1517

1618
func New(i interface{}) *Config {

0 commit comments

Comments
 (0)