mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
env[] keyword implemented in parser (#382)
This commit is contained in:
+11
-1
@@ -383,8 +383,18 @@ func (v *visitor) ChainNode(node *ast.ChainNode) (reflect.Type, info) {
|
||||
}
|
||||
|
||||
func (v *visitor) MemberNode(node *ast.MemberNode) (reflect.Type, info) {
|
||||
base, _ := v.visit(node.Node)
|
||||
prop, _ := v.visit(node.Property)
|
||||
if an, ok := node.Node.(*ast.IdentifierNode); ok && an.Value == "env" {
|
||||
// If the index is a constant string, can save some
|
||||
// cycles later by finding the type of its referent
|
||||
if name, ok := node.Property.(*ast.StringNode); ok {
|
||||
if t, ok := v.config.Types[name.Value]; ok {
|
||||
return t.Type, info{method: t.Method}
|
||||
} // No error if no type found; it may be added to env between compile and run
|
||||
}
|
||||
return anyType, info{}
|
||||
}
|
||||
base, _ := v.visit(node.Node)
|
||||
|
||||
if name, ok := node.Property.(*ast.StringNode); ok {
|
||||
if base == nil {
|
||||
|
||||
@@ -205,6 +205,10 @@ func (c *compiler) NilNode(_ *ast.NilNode) {
|
||||
}
|
||||
|
||||
func (c *compiler) IdentifierNode(node *ast.IdentifierNode) {
|
||||
if node.Value == "env" {
|
||||
c.emit(OpLoadEnv)
|
||||
return
|
||||
}
|
||||
if c.mapEnv {
|
||||
c.emit(OpLoadFast, c.addConstant(node.Value))
|
||||
} else if len(node.FieldIndex) > 0 {
|
||||
|
||||
+10
-4
@@ -54,6 +54,9 @@ func CreateTypesTable(i interface{}) TypesTable {
|
||||
for _, key := range v.MapKeys() {
|
||||
value := v.MapIndex(key)
|
||||
if key.Kind() == reflect.String && value.IsValid() && value.CanInterface() {
|
||||
if key.String() == "env" { // Could check for all keywords here
|
||||
panic("attempt to misuse env keyword as env map key")
|
||||
}
|
||||
types[key.String()] = Tag{Type: reflect.TypeOf(value.Interface())}
|
||||
}
|
||||
}
|
||||
@@ -94,10 +97,13 @@ func FieldsFromStruct(t reflect.Type) TypesTable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
types[FieldName(f)] = Tag{
|
||||
Type: f.Type,
|
||||
FieldIndex: f.Index,
|
||||
if fn := FieldName(f); fn == "env" { // Could check for all keywords here
|
||||
panic("attempt to misuse env keyword as env struct field tag")
|
||||
} else {
|
||||
types[FieldName(f)] = Tag{
|
||||
Type: f.Type,
|
||||
FieldIndex: f.Index,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1811,6 +1811,103 @@ func TestEval_nil_in_maps(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Test the use of env keyword. Forms env[] and env[”] are valid.
|
||||
// The enclosed identifier must be in the expression env.
|
||||
func TestEnv_keyword(t *testing.T) {
|
||||
env := map[string]interface{}{
|
||||
"space test": "ok",
|
||||
"space_test": "not ok", // Seems to be some underscore substituting happening, check that.
|
||||
"Section 1-2a": "ok",
|
||||
`c:\ndrive\2015 Information Table`: "ok",
|
||||
"%*worst function name ever!!": func() string {
|
||||
return "ok"
|
||||
}(),
|
||||
"1": "o",
|
||||
"2": "k",
|
||||
"num": 10,
|
||||
"mylist": []int{1, 2, 3, 4, 5},
|
||||
"MIN": func(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
},
|
||||
"red": "n",
|
||||
"irect": "um",
|
||||
"String Map": map[string]string{
|
||||
"one": "two",
|
||||
"three": "four",
|
||||
},
|
||||
"OtherMap": map[string]string{
|
||||
"a": "b",
|
||||
"c": "d",
|
||||
},
|
||||
}
|
||||
|
||||
// No error cases
|
||||
var tests = []struct {
|
||||
code string
|
||||
want interface{}
|
||||
}{
|
||||
{"env['space test']", "ok"},
|
||||
{"env['Section 1-2a']", "ok"},
|
||||
{`env["c:\\ndrive\\2015 Information Table"]`, "ok"},
|
||||
{"env['%*worst function name ever!!']", "ok"},
|
||||
{"env['String Map'].one", "two"},
|
||||
{"env['1'] + env['2']", "ok"},
|
||||
{"1 + env['num'] + env['num']", 21},
|
||||
{"MIN(env['num'],0)", 0},
|
||||
{"env['nu' + 'm']", 10},
|
||||
{"env[red + irect]", 10},
|
||||
{"env['String Map']?.five", ""},
|
||||
{"env.red", "n"},
|
||||
{"env?.blue", nil},
|
||||
{"env.mylist[1]", 2},
|
||||
{"env?.OtherMap?.a", "b"},
|
||||
{"env?.OtherMap?.d", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.code, func(t *testing.T) {
|
||||
|
||||
program, err := expr.Compile(tt.code, expr.Env(env))
|
||||
require.NoError(t, err, "compile error")
|
||||
|
||||
got, err := expr.Run(program, env)
|
||||
require.NoError(t, err, "execution error")
|
||||
|
||||
assert.Equal(t, tt.want, got, tt.code)
|
||||
})
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.code, func(t *testing.T) {
|
||||
got, err := expr.Eval(tt.code, env)
|
||||
require.NoError(t, err, "eval error: "+tt.code)
|
||||
|
||||
assert.Equal(t, tt.want, got, "eval: "+tt.code)
|
||||
})
|
||||
}
|
||||
|
||||
// error cases
|
||||
tests = []struct {
|
||||
code string
|
||||
want interface{}
|
||||
}{
|
||||
{"env()", "bad"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.code, func(t *testing.T) {
|
||||
_, err := expr.Eval(tt.code, expr.Env(env))
|
||||
require.Error(t, err, "compile error")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type Bar interface {
|
||||
Bar() int
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
OpLoadFast
|
||||
OpLoadMethod
|
||||
OpLoadFunc
|
||||
OpLoadEnv
|
||||
OpFetch
|
||||
OpFetchField
|
||||
OpMethod
|
||||
|
||||
@@ -123,6 +123,8 @@ func (vm *VM) Run(program *Program, env interface{}) (_ interface{}, err error)
|
||||
a := vm.pop()
|
||||
vm.push(runtime.FetchField(a, program.Constants[arg].(*runtime.Field)))
|
||||
|
||||
case OpLoadEnv:
|
||||
vm.push(env)
|
||||
case OpMethod:
|
||||
a := vm.pop()
|
||||
vm.push(runtime.FetchMethod(a, program.Constants[arg].(*runtime.Method)))
|
||||
|
||||
Reference in New Issue
Block a user