Skip to content

Commit 81bb58d

Browse files
committed
Add tests
1 parent e73f3bc commit 81bb58d

File tree

3 files changed

+133
-37
lines changed

3 files changed

+133
-37
lines changed

compiler/compiler.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -441,8 +441,10 @@ func (c *compiler) MemberNode(node *ast.MemberNode) {
441441
}
442442
index = append(ident.Index, index...)
443443
path = ident.Value + "." + path
444-
op = OpFetchEnvField
445-
goto emit
444+
c.emitLoc(ident, OpFetchEnvField, c.makeConstant(
445+
&runtime.Field{Index: index, Path: path},
446+
)...)
447+
goto deref
446448
}
447449
member, ok := base.(*ast.MemberNode)
448450
if ok && len(member.Index) > 0 {
@@ -465,13 +467,16 @@ func (c *compiler) MemberNode(node *ast.MemberNode) {
465467
c.chains[len(c.chains)-1] = append(c.chains[len(c.chains)-1], ph)
466468
}
467469

468-
emit:
469470
if op == OpFetch {
470471
c.compile(node.Property)
471472
c.emit(OpFetch)
472473
} else {
473-
c.emitLoc(node, op, c.makeConstant(&runtime.Field{Index: index, Path: path})...)
474+
c.emitLoc(node, op, c.makeConstant(
475+
&runtime.Field{Index: index, Path: path},
476+
)...)
474477
}
478+
479+
deref:
475480
if original.Deref {
476481
c.emit(OpDeref)
477482
}

compiler/compiler_test.go

Lines changed: 98 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package compiler_test
22

33
import (
4+
"github.com/antonmedv/expr"
5+
"github.com/antonmedv/expr/vm/runtime"
46
"math"
57
"reflect"
68
"testing"
@@ -13,14 +15,23 @@ import (
1315
"github.com/stretchr/testify/require"
1416
)
1517

16-
func TestCompile_debug(t *testing.T) {
17-
input := `false && true && true`
18-
19-
tree, err := parser.Parse(input)
20-
require.NoError(t, err)
18+
type B struct {
19+
_ byte
20+
_ byte
21+
C struct {
22+
_ byte
23+
_ byte
24+
_ byte
25+
D int
26+
}
27+
}
2128

22-
_, err = compiler.Compile(tree, nil)
23-
require.NoError(t, err)
29+
type Env struct {
30+
A struct {
31+
_ byte
32+
B B
33+
Map map[string]B
34+
}
2435
}
2536

2637
func TestCompile(t *testing.T) {
@@ -59,17 +70,6 @@ func TestCompile(t *testing.T) {
5970
},
6071
},
6172
},
62-
{
63-
`Name`,
64-
vm.Program{
65-
Constants: []interface{}{
66-
"Name",
67-
},
68-
Bytecode: []byte{
69-
vm.OpFetchEnv, 0, 0,
70-
},
71-
},
72-
},
7373
{
7474
`"string"`,
7575
vm.Program{
@@ -90,7 +90,7 @@ func TestCompile(t *testing.T) {
9090
Bytecode: []byte{
9191
vm.OpPush, 0, 0,
9292
vm.OpPush, 0, 0,
93-
vm.OpEqual,
93+
vm.OpEqualString,
9494
},
9595
},
9696
},
@@ -103,17 +103,16 @@ func TestCompile(t *testing.T) {
103103
Bytecode: []byte{
104104
vm.OpPush, 0, 0,
105105
vm.OpPush, 0, 0,
106-
vm.OpEqual,
106+
vm.OpEqualInt,
107107
},
108108
},
109109
},
110110
{
111111
`-1`,
112112
vm.Program{
113-
Constants: []interface{}{1},
113+
Constants: []interface{}{-1},
114114
Bytecode: []byte{
115115
vm.OpPush, 0, 0,
116-
vm.OpNegate,
117116
},
118117
},
119118
},
@@ -131,13 +130,86 @@ func TestCompile(t *testing.T) {
131130
},
132131
},
133132
},
133+
{
134+
`A.B.C.D`,
135+
vm.Program{
136+
Constants: []interface{}{
137+
&runtime.Field{
138+
Index: []int{0, 1, 2, 3},
139+
Path: "A.B.C.D",
140+
},
141+
},
142+
Bytecode: []byte{
143+
vm.OpFetchEnvField, 0, 0,
144+
},
145+
},
146+
},
147+
{
148+
`A?.B.C.D`,
149+
vm.Program{
150+
Constants: []interface{}{
151+
&runtime.Field{
152+
Index: []int{0},
153+
Path: "A",
154+
},
155+
&runtime.Field{
156+
Index: []int{1, 2, 3},
157+
Path: "B.C.D",
158+
},
159+
},
160+
Bytecode: []byte{
161+
vm.OpFetchEnvField, 0, 0,
162+
vm.OpJumpIfNil, 3, 0,
163+
vm.OpFetchField, 1, 0,
164+
},
165+
},
166+
},
167+
{
168+
`A.B?.C.D`,
169+
vm.Program{
170+
Constants: []interface{}{
171+
&runtime.Field{
172+
Index: []int{0, 1},
173+
Path: "A.B",
174+
},
175+
&runtime.Field{
176+
Index: []int{2, 3},
177+
Path: "C.D",
178+
},
179+
},
180+
Bytecode: []byte{
181+
vm.OpFetchEnvField, 0, 0,
182+
vm.OpJumpIfNil, 3, 0,
183+
vm.OpFetchField, 1, 0,
184+
},
185+
},
186+
},
187+
{
188+
`A.Map["B"].C.D`,
189+
vm.Program{
190+
Constants: []interface{}{
191+
&runtime.Field{
192+
Index: []int{0, 2},
193+
Path: "A.Map",
194+
},
195+
"B",
196+
&runtime.Field{
197+
Index: []int{2, 3},
198+
Path: "C.D",
199+
},
200+
},
201+
Bytecode: []byte{
202+
vm.OpFetchEnvField, 0, 0,
203+
vm.OpPush, 1, 0,
204+
vm.OpFetch,
205+
vm.OpFetchField, 2, 0,
206+
},
207+
},
208+
},
134209
}
135210

136211
for _, test := range tests {
137-
node, err := parser.Parse(test.input)
138-
require.NoError(t, err)
139-
140-
program, err := compiler.Compile(node, nil)
212+
program, err := expr.Compile(test.input, expr.Env(Env{}))
141213
require.NoError(t, err, test.input)
142214

143215
assert.Equal(t, test.program.Disassemble(), program.Disassemble(), test.input)

vm/runtime/runtime.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,6 @@ type Field struct {
7272
}
7373

7474
func FetchField(from interface{}, field *Field) interface{} {
75-
defer func() {
76-
if r := recover(); r != nil {
77-
panic(fmt.Sprintf("cannot get %v from %T", field.Path, from))
78-
}
79-
}()
8075
v := reflect.ValueOf(from)
8176
kind := v.Kind()
8277
if kind != reflect.Invalid {
@@ -85,13 +80,37 @@ func FetchField(from interface{}, field *Field) interface{} {
8580
kind = v.Kind()
8681
}
8782
if kind == reflect.Struct {
88-
value := v.FieldByIndex(field.Index)
83+
// We can use v.FieldByIndex here, but it will panic if the field
84+
// is not exists. And we need to recover() to generate a more
85+
// user-friendly error message.
86+
// Also, our fieldByIndex() function is slightly faster than the
87+
// v.FieldByIndex() function as we don't need to verify what a field
88+
// is a struct as we already did it on compilation step.
89+
value := fieldByIndex(v, field.Index)
8990
if value.IsValid() && value.CanInterface() {
9091
return value.Interface()
9192
}
9293
}
9394
}
94-
panic("why not")
95+
panic(fmt.Sprintf("cannot get %v from %T", field.Path, from))
96+
}
97+
98+
func fieldByIndex(v reflect.Value, index []int) reflect.Value {
99+
if len(index) == 1 {
100+
return v.Field(index[0])
101+
}
102+
for i, x := range index {
103+
if i > 0 {
104+
if v.Kind() == reflect.Ptr {
105+
if v.IsNil() {
106+
return reflect.Value{}
107+
}
108+
v = v.Elem()
109+
}
110+
}
111+
v = v.Field(x)
112+
}
113+
return v
95114
}
96115

97116
func Deref(i interface{}) interface{} {

0 commit comments

Comments
 (0)