Skip to content

Commit 3f73a21

Browse files
authored
Merge branch 'antonmedv:master' into master
2 parents 6432feb + d2100ec commit 3f73a21

File tree

11 files changed

+154
-11
lines changed

11 files changed

+154
-11
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
<a href="https://webpod.dev/?from=expr"><img src="https://webpod.dev/img/banner.png" alt="Webpod - deploy JavaScript apps" width="190" align="right"></a>
2+
13
# Expr
24
[![test](https://github.com/antonmedv/expr/actions/workflows/test.yml/badge.svg)](https://github.com/antonmedv/expr/actions/workflows/test.yml)
35
[![Go Report Card](https://goreportcard.com/badge/github.com/antonmedv/expr)](https://goreportcard.com/report/github.com/antonmedv/expr)

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
}

file/error.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type Error struct {
1010
Location
1111
Message string
1212
Snippet string
13-
Prev error
13+
Prev error
1414
}
1515

1616
func (e *Error) Error() string {
@@ -45,7 +45,6 @@ func (e *Error) Bind(source *Source) *Error {
4545
return e
4646
}
4747

48-
4948
func (e *Error) Unwrap() error {
5049
return e.Prev
5150
}
@@ -54,7 +53,6 @@ func (e *Error) Wrap(err error) {
5453
e.Prev = err
5554
}
5655

57-
5856
func (e *Error) format() string {
5957
if e.Location.Empty() {
6058
return e.Message

vm/opcodes.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ package vm
33
type Opcode byte
44

55
const (
6-
OpPush Opcode = iota
6+
OpInvalid Opcode = iota
7+
OpPush
78
OpPushInt
89
OpPop
910
OpLoadConst
1011
OpLoadField
1112
OpLoadFast
1213
OpLoadMethod
1314
OpLoadFunc
15+
OpLoadEnv
1416
OpFetch
1517
OpFetchField
1618
OpMethod

vm/program.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ func (program *Program) Disassemble() string {
7373
}
7474

7575
switch op {
76+
case OpInvalid:
77+
code("OpInvalid")
78+
7679
case OpPush:
7780
constant("OpPush")
7881

@@ -97,6 +100,9 @@ func (program *Program) Disassemble() string {
97100
case OpLoadFunc:
98101
argument("OpLoadFunc")
99102

103+
case OpLoadEnv:
104+
code("OpLoadEnv")
105+
100106
case OpFetch:
101107
code("OpFetch")
102108

@@ -270,7 +276,7 @@ func (program *Program) Disassemble() string {
270276
code("OpEnd")
271277

272278
default:
273-
_, _ = fmt.Fprintf(w, "%v\t%#x\n", ip, op)
279+
_, _ = fmt.Fprintf(w, "%v\t%#x (unknown)\n", ip, op)
274280
}
275281
}
276282
_ = w.Flush()

vm/program_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func TestProgram_Disassemble(t *testing.T) {
1515
Arguments: []int{1},
1616
}
1717
d := program.Disassemble()
18-
if strings.Contains(d, "\t0x") {
18+
if strings.Contains(d, "(unknown)") {
1919
t.Errorf("cannot disassemble all opcodes")
2020
}
2121
}

vm/vm.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ func (vm *VM) Run(program *Program, env interface{}) (_ interface{}, err error)
9393

9494
switch op {
9595

96+
case OpInvalid:
97+
panic("invalid opcode")
98+
9699
case OpPush:
97100
vm.push(program.Constants[arg])
98101

@@ -123,6 +126,9 @@ func (vm *VM) Run(program *Program, env interface{}) (_ interface{}, err error)
123126
a := vm.pop()
124127
vm.push(runtime.FetchField(a, program.Constants[arg].(*runtime.Field)))
125128

129+
case OpLoadEnv:
130+
vm.push(env)
131+
126132
case OpMethod:
127133
a := vm.pop()
128134
vm.push(runtime.FetchMethod(a, program.Constants[arg].(*runtime.Method)))

0 commit comments

Comments
 (0)