Skip to content

Commit

Permalink
env[] keyword implemented in parser (#382)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdmcconnell authored Jul 12, 2023
1 parent d41c494 commit 3c23d10
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 5 deletions.
12 changes: 11 additions & 1 deletion checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 10 additions & 4 deletions conf/types_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())}
}
}
Expand Down Expand Up @@ -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,
}
}
}
}
Expand Down
97 changes: 97 additions & 0 deletions expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions vm/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
OpLoadFast
OpLoadMethod
OpLoadFunc
OpLoadEnv
OpFetch
OpFetchField
OpMethod
Expand Down
2 changes: 2 additions & 0 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down

0 comments on commit 3c23d10

Please sign in to comment.