Skip to content

Commit 3c23d10

Browse files
authored
env[] keyword implemented in parser (#382)
1 parent d41c494 commit 3c23d10

File tree

6 files changed

+125
-5
lines changed

6 files changed

+125
-5
lines changed

checker/checker.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,8 +383,18 @@ func (v *visitor) ChainNode(node *ast.ChainNode) (reflect.Type, info) {
383383
}
384384

385385
func (v *visitor) MemberNode(node *ast.MemberNode) (reflect.Type, info) {
386-
base, _ := v.visit(node.Node)
387386
prop, _ := v.visit(node.Property)
387+
if an, ok := node.Node.(*ast.IdentifierNode); ok && an.Value == "env" {
388+
// If the index is a constant string, can save some
389+
// cycles later by finding the type of its referent
390+
if name, ok := node.Property.(*ast.StringNode); ok {
391+
if t, ok := v.config.Types[name.Value]; ok {
392+
return t.Type, info{method: t.Method}
393+
} // No error if no type found; it may be added to env between compile and run
394+
}
395+
return anyType, info{}
396+
}
397+
base, _ := v.visit(node.Node)
388398

389399
if name, ok := node.Property.(*ast.StringNode); ok {
390400
if base == nil {

compiler/compiler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ func (c *compiler) NilNode(_ *ast.NilNode) {
205205
}
206206

207207
func (c *compiler) IdentifierNode(node *ast.IdentifierNode) {
208+
if node.Value == "env" {
209+
c.emit(OpLoadEnv)
210+
return
211+
}
208212
if c.mapEnv {
209213
c.emit(OpLoadFast, c.addConstant(node.Value))
210214
} else if len(node.FieldIndex) > 0 {

conf/types_table.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ func CreateTypesTable(i interface{}) TypesTable {
5454
for _, key := range v.MapKeys() {
5555
value := v.MapIndex(key)
5656
if key.Kind() == reflect.String && value.IsValid() && value.CanInterface() {
57+
if key.String() == "env" { // Could check for all keywords here
58+
panic("attempt to misuse env keyword as env map key")
59+
}
5760
types[key.String()] = Tag{Type: reflect.TypeOf(value.Interface())}
5861
}
5962
}
@@ -94,10 +97,13 @@ func FieldsFromStruct(t reflect.Type) TypesTable {
9497
}
9598
}
9699
}
97-
98-
types[FieldName(f)] = Tag{
99-
Type: f.Type,
100-
FieldIndex: f.Index,
100+
if fn := FieldName(f); fn == "env" { // Could check for all keywords here
101+
panic("attempt to misuse env keyword as env struct field tag")
102+
} else {
103+
types[FieldName(f)] = Tag{
104+
Type: f.Type,
105+
FieldIndex: f.Index,
106+
}
101107
}
102108
}
103109
}

expr_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,103 @@ func TestEval_nil_in_maps(t *testing.T) {
18111811
})
18121812
}
18131813

1814+
// Test the use of env keyword. Forms env[] and env[”] are valid.
1815+
// The enclosed identifier must be in the expression env.
1816+
func TestEnv_keyword(t *testing.T) {
1817+
env := map[string]interface{}{
1818+
"space test": "ok",
1819+
"space_test": "not ok", // Seems to be some underscore substituting happening, check that.
1820+
"Section 1-2a": "ok",
1821+
`c:\ndrive\2015 Information Table`: "ok",
1822+
"%*worst function name ever!!": func() string {
1823+
return "ok"
1824+
}(),
1825+
"1": "o",
1826+
"2": "k",
1827+
"num": 10,
1828+
"mylist": []int{1, 2, 3, 4, 5},
1829+
"MIN": func(a, b int) int {
1830+
if a < b {
1831+
return a
1832+
} else {
1833+
return b
1834+
}
1835+
},
1836+
"red": "n",
1837+
"irect": "um",
1838+
"String Map": map[string]string{
1839+
"one": "two",
1840+
"three": "four",
1841+
},
1842+
"OtherMap": map[string]string{
1843+
"a": "b",
1844+
"c": "d",
1845+
},
1846+
}
1847+
1848+
// No error cases
1849+
var tests = []struct {
1850+
code string
1851+
want interface{}
1852+
}{
1853+
{"env['space test']", "ok"},
1854+
{"env['Section 1-2a']", "ok"},
1855+
{`env["c:\\ndrive\\2015 Information Table"]`, "ok"},
1856+
{"env['%*worst function name ever!!']", "ok"},
1857+
{"env['String Map'].one", "two"},
1858+
{"env['1'] + env['2']", "ok"},
1859+
{"1 + env['num'] + env['num']", 21},
1860+
{"MIN(env['num'],0)", 0},
1861+
{"env['nu' + 'm']", 10},
1862+
{"env[red + irect]", 10},
1863+
{"env['String Map']?.five", ""},
1864+
{"env.red", "n"},
1865+
{"env?.blue", nil},
1866+
{"env.mylist[1]", 2},
1867+
{"env?.OtherMap?.a", "b"},
1868+
{"env?.OtherMap?.d", ""},
1869+
}
1870+
1871+
for _, tt := range tests {
1872+
t.Run(tt.code, func(t *testing.T) {
1873+
1874+
program, err := expr.Compile(tt.code, expr.Env(env))
1875+
require.NoError(t, err, "compile error")
1876+
1877+
got, err := expr.Run(program, env)
1878+
require.NoError(t, err, "execution error")
1879+
1880+
assert.Equal(t, tt.want, got, tt.code)
1881+
})
1882+
}
1883+
1884+
for _, tt := range tests {
1885+
t.Run(tt.code, func(t *testing.T) {
1886+
got, err := expr.Eval(tt.code, env)
1887+
require.NoError(t, err, "eval error: "+tt.code)
1888+
1889+
assert.Equal(t, tt.want, got, "eval: "+tt.code)
1890+
})
1891+
}
1892+
1893+
// error cases
1894+
tests = []struct {
1895+
code string
1896+
want interface{}
1897+
}{
1898+
{"env()", "bad"},
1899+
}
1900+
1901+
for _, tt := range tests {
1902+
t.Run(tt.code, func(t *testing.T) {
1903+
_, err := expr.Eval(tt.code, expr.Env(env))
1904+
require.Error(t, err, "compile error")
1905+
1906+
})
1907+
}
1908+
1909+
}
1910+
18141911
type Bar interface {
18151912
Bar() int
18161913
}

vm/opcodes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const (
1111
OpLoadFast
1212
OpLoadMethod
1313
OpLoadFunc
14+
OpLoadEnv
1415
OpFetch
1516
OpFetchField
1617
OpMethod

vm/vm.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ func (vm *VM) Run(program *Program, env interface{}) (_ interface{}, err error)
123123
a := vm.pop()
124124
vm.push(runtime.FetchField(a, program.Constants[arg].(*runtime.Field)))
125125

126+
case OpLoadEnv:
127+
vm.push(env)
126128
case OpMethod:
127129
a := vm.pop()
128130
vm.push(runtime.FetchMethod(a, program.Constants[arg].(*runtime.Method)))

0 commit comments

Comments
 (0)