Add tests

This commit is contained in:
Anton Medvedev
2022-10-21 21:48:18 +02:00
parent dd65a0fc2d
commit b77102221c
3 changed files with 133 additions and 37 deletions
+9 -4
View File
@@ -441,8 +441,10 @@ func (c *compiler) MemberNode(node *ast.MemberNode) {
}
index = append(ident.Index, index...)
path = ident.Value + "." + path
op = OpFetchEnvField
goto emit
c.emitLoc(ident, OpFetchEnvField, c.makeConstant(
&runtime.Field{Index: index, Path: path},
)...)
goto deref
}
member, ok := base.(*ast.MemberNode)
if ok && len(member.Index) > 0 {
@@ -465,13 +467,16 @@ func (c *compiler) MemberNode(node *ast.MemberNode) {
c.chains[len(c.chains)-1] = append(c.chains[len(c.chains)-1], ph)
}
emit:
if op == OpFetch {
c.compile(node.Property)
c.emit(OpFetch)
} else {
c.emitLoc(node, op, c.makeConstant(&runtime.Field{Index: index, Path: path})...)
c.emitLoc(node, op, c.makeConstant(
&runtime.Field{Index: index, Path: path},
)...)
}
deref:
if original.Deref {
c.emit(OpDeref)
}
+98 -26
View File
@@ -1,6 +1,8 @@
package compiler_test
import (
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/vm/runtime"
"math"
"reflect"
"testing"
@@ -13,14 +15,23 @@ import (
"github.com/stretchr/testify/require"
)
func TestCompile_debug(t *testing.T) {
input := `false && true && true`
type B struct {
_ byte
_ byte
C struct {
_ byte
_ byte
_ byte
D int
}
}
tree, err := parser.Parse(input)
require.NoError(t, err)
_, err = compiler.Compile(tree, nil)
require.NoError(t, err)
type Env struct {
A struct {
_ byte
B B
Map map[string]B
}
}
func TestCompile(t *testing.T) {
@@ -59,17 +70,6 @@ func TestCompile(t *testing.T) {
},
},
},
{
`Name`,
vm.Program{
Constants: []interface{}{
"Name",
},
Bytecode: []byte{
vm.OpFetchEnv, 0, 0,
},
},
},
{
`"string"`,
vm.Program{
@@ -90,7 +90,7 @@ func TestCompile(t *testing.T) {
Bytecode: []byte{
vm.OpPush, 0, 0,
vm.OpPush, 0, 0,
vm.OpEqual,
vm.OpEqualString,
},
},
},
@@ -103,17 +103,16 @@ func TestCompile(t *testing.T) {
Bytecode: []byte{
vm.OpPush, 0, 0,
vm.OpPush, 0, 0,
vm.OpEqual,
vm.OpEqualInt,
},
},
},
{
`-1`,
vm.Program{
Constants: []interface{}{1},
Constants: []interface{}{-1},
Bytecode: []byte{
vm.OpPush, 0, 0,
vm.OpNegate,
},
},
},
@@ -131,13 +130,86 @@ func TestCompile(t *testing.T) {
},
},
},
{
`A.B.C.D`,
vm.Program{
Constants: []interface{}{
&runtime.Field{
Index: []int{0, 1, 2, 3},
Path: "A.B.C.D",
},
},
Bytecode: []byte{
vm.OpFetchEnvField, 0, 0,
},
},
},
{
`A?.B.C.D`,
vm.Program{
Constants: []interface{}{
&runtime.Field{
Index: []int{0},
Path: "A",
},
&runtime.Field{
Index: []int{1, 2, 3},
Path: "B.C.D",
},
},
Bytecode: []byte{
vm.OpFetchEnvField, 0, 0,
vm.OpJumpIfNil, 3, 0,
vm.OpFetchField, 1, 0,
},
},
},
{
`A.B?.C.D`,
vm.Program{
Constants: []interface{}{
&runtime.Field{
Index: []int{0, 1},
Path: "A.B",
},
&runtime.Field{
Index: []int{2, 3},
Path: "C.D",
},
},
Bytecode: []byte{
vm.OpFetchEnvField, 0, 0,
vm.OpJumpIfNil, 3, 0,
vm.OpFetchField, 1, 0,
},
},
},
{
`A.Map["B"].C.D`,
vm.Program{
Constants: []interface{}{
&runtime.Field{
Index: []int{0, 2},
Path: "A.Map",
},
"B",
&runtime.Field{
Index: []int{2, 3},
Path: "C.D",
},
},
Bytecode: []byte{
vm.OpFetchEnvField, 0, 0,
vm.OpPush, 1, 0,
vm.OpFetch,
vm.OpFetchField, 2, 0,
},
},
},
}
for _, test := range tests {
node, err := parser.Parse(test.input)
require.NoError(t, err)
program, err := compiler.Compile(node, nil)
program, err := expr.Compile(test.input, expr.Env(Env{}))
require.NoError(t, err, test.input)
assert.Equal(t, test.program.Disassemble(), program.Disassemble(), test.input)
+26 -7
View File
@@ -72,11 +72,6 @@ type Field struct {
}
func FetchField(from interface{}, field *Field) interface{} {
defer func() {
if r := recover(); r != nil {
panic(fmt.Sprintf("cannot get %v from %T", field.Path, from))
}
}()
v := reflect.ValueOf(from)
kind := v.Kind()
if kind != reflect.Invalid {
@@ -85,13 +80,37 @@ func FetchField(from interface{}, field *Field) interface{} {
kind = v.Kind()
}
if kind == reflect.Struct {
value := v.FieldByIndex(field.Index)
// We can use v.FieldByIndex here, but it will panic if the field
// is not exists. And we need to recover() to generate a more
// user-friendly error message.
// Also, our fieldByIndex() function is slightly faster than the
// v.FieldByIndex() function as we don't need to verify what a field
// is a struct as we already did it on compilation step.
value := fieldByIndex(v, field.Index)
if value.IsValid() && value.CanInterface() {
return value.Interface()
}
}
}
panic("why not")
panic(fmt.Sprintf("cannot get %v from %T", field.Path, from))
}
func fieldByIndex(v reflect.Value, index []int) reflect.Value {
if len(index) == 1 {
return v.Field(index[0])
}
for i, x := range index {
if i > 0 {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return reflect.Value{}
}
v = v.Elem()
}
}
v = v.Field(x)
}
return v
}
func Deref(i interface{}) interface{} {