mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Add tests
This commit is contained in:
@@ -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
@@ -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
@@ -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{} {
|
||||
|
||||
Reference in New Issue
Block a user