Skip to content

Commit b5c2bd0

Browse files
committed
Fix OpCall
1 parent 115dc2a commit b5c2bd0

File tree

5 files changed

+90
-79
lines changed

5 files changed

+90
-79
lines changed

checker/checker.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ func (v *visitor) IdentifierNode(node *ast.IdentifierNode) (reflect.Type, info)
131131
if t.Ambiguous {
132132
return v.error(node, "ambiguous identifier %v", node.Value)
133133
}
134-
return t.Type, info{}
134+
return t.Type, info{method: t.Method}
135135
}
136136
if !v.strict {
137137
if v.defaultType != nil {
@@ -458,6 +458,10 @@ func (v *visitor) checkFunc(fn reflect.Type, method bool, node ast.Node, name st
458458
setTypeForIntegers(arg, t)
459459
}
460460

461+
if t == nil {
462+
continue
463+
}
464+
461465
if !t.AssignableTo(in) && t.Kind() != reflect.Interface {
462466
return v.error(arg, "cannot use %v as argument (type %v) to call %v ", t, in, name)
463467
}

expr_test.go

+77-70
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"fmt"
66
"github.com/antonmedv/expr/ast"
7+
"reflect"
78
"strings"
89
"testing"
910
"time"
@@ -495,9 +496,9 @@ func TestExpr_readme_example(t *testing.T) {
495496

496497
func TestExpr(t *testing.T) {
497498
date := time.Date(2017, time.October, 23, 18, 30, 0, 0, time.UTC)
498-
tnow := time.Now()
499+
timeNow := time.Now()
499500
oneDay, _ := time.ParseDuration("24h")
500-
tnowPlusOne := tnow.Add(oneDay)
501+
timeNowPlusOneDay := timeNow.Add(oneDay)
501502

502503
env := &mockEnv{
503504
Any: "any",
@@ -520,8 +521,8 @@ func TestExpr(t *testing.T) {
520521
{Origin: "LED", Destination: "MOW"},
521522
},
522523
BirthDay: date,
523-
Now: tnow,
524-
NowPlusOne: tnowPlusOne,
524+
Now: timeNow,
525+
NowPlusOne: timeNowPlusOneDay,
525526
OneDayDuration: oneDay,
526527
One: 1,
527528
Two: 2,
@@ -950,11 +951,11 @@ func TestExpr(t *testing.T) {
950951
},
951952
{
952953
`Now + OneDayDuration`,
953-
tnowPlusOne,
954+
timeNowPlusOneDay,
954955
},
955956
{
956957
`OneDayDuration + Now`,
957-
tnowPlusOne,
958+
timeNowPlusOneDay,
958959
},
959960
{
960961
`lowercase`,
@@ -973,6 +974,9 @@ func TestExpr(t *testing.T) {
973974
}
974975

975976
for _, tt := range tests {
977+
if tt.code == "map(filter(Tweets, {len(.Text) > 10}), {Format(.Date)})" {
978+
expr.Compile(tt.code, expr.Optimize(false))
979+
}
976980
program, err := expr.Compile(tt.code, expr.Optimize(false))
977981
require.NoError(t, err, "compile error")
978982

@@ -1299,35 +1303,70 @@ func TestConstExpr_error_no_env(t *testing.T) {
12991303
require.Equal(t, "no environment for const expression: divide", err.Error())
13001304
}
13011305

1302-
//func TestPatch(t *testing.T) {
1303-
// program, err := expr.Compile(
1304-
// `Ticket == "$100" and "$90" != Ticket + "0"`,
1305-
// expr.Env(mockEnv{}),
1306-
// expr.Patch(&stringerPatcher{}),
1307-
// )
1308-
// require.NoError(t, err)
1309-
//
1310-
// env := mockEnv{
1311-
// Ticket: &ticket{Price: 100},
1312-
// }
1313-
// output, err := expr.Run(program, env)
1314-
// require.NoError(t, err)
1315-
// require.Equal(t, true, output)
1316-
//}
1317-
1318-
//func TestPatch_length(t *testing.T) {
1319-
// program, err := expr.Compile(
1320-
// `String.length == 5`,
1321-
// expr.Env(mockEnv{}),
1322-
// expr.Patch(&lengthPatcher{}),
1323-
// )
1324-
// require.NoError(t, err)
1325-
//
1326-
// env := mockEnv{String: "hello"}
1327-
// output, err := expr.Run(program, env)
1328-
// require.NoError(t, err)
1329-
// require.Equal(t, true, output)
1330-
//}
1306+
var stringer = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
1307+
1308+
type stringerPatcher struct{}
1309+
1310+
func (p *stringerPatcher) Enter(_ *ast.Node) {}
1311+
func (p *stringerPatcher) Exit(node *ast.Node) {
1312+
t := (*node).Type()
1313+
if t == nil {
1314+
return
1315+
}
1316+
if t.Implements(stringer) {
1317+
ast.Patch(node, &ast.CallNode{
1318+
Callee: &ast.MemberNode{
1319+
Node: *node,
1320+
Property: &ast.StringNode{Value: "String"},
1321+
},
1322+
})
1323+
}
1324+
}
1325+
1326+
func TestPatch(t *testing.T) {
1327+
program, err := expr.Compile(
1328+
`Ticket == "$100" and "$90" != Ticket + "0"`,
1329+
expr.Env(mockEnv{}),
1330+
expr.Patch(&stringerPatcher{}),
1331+
)
1332+
require.NoError(t, err)
1333+
1334+
env := mockEnv{
1335+
Ticket: &ticket{Price: 100},
1336+
}
1337+
output, err := expr.Run(program, env)
1338+
require.NoError(t, err)
1339+
require.Equal(t, true, output)
1340+
}
1341+
1342+
type lengthPatcher struct{}
1343+
1344+
func (p *lengthPatcher) Enter(_ *ast.Node) {}
1345+
func (p *lengthPatcher) Exit(node *ast.Node) {
1346+
switch n := (*node).(type) {
1347+
case *ast.MemberNode:
1348+
if prop, ok := n.Property.(*ast.StringNode); ok && prop.Value == "length" {
1349+
ast.Patch(node, &ast.BuiltinNode{
1350+
Name: "len",
1351+
Arguments: []ast.Node{n.Node},
1352+
})
1353+
}
1354+
}
1355+
}
1356+
1357+
func TestPatch_length(t *testing.T) {
1358+
program, err := expr.Compile(
1359+
`String.length == 5`,
1360+
expr.Env(mockEnv{}),
1361+
expr.Patch(&lengthPatcher{}),
1362+
)
1363+
require.NoError(t, err)
1364+
1365+
env := mockEnv{String: "hello"}
1366+
output, err := expr.Run(program, env)
1367+
require.NoError(t, err)
1368+
require.Equal(t, true, output)
1369+
}
13311370

13321371
func TestCompile_exposed_error(t *testing.T) {
13331372
_, err := expr.Compile(`1 == true`)
@@ -1516,7 +1555,9 @@ func (*mockEnv) Float(i interface{}) float64 {
15161555
}
15171556
}
15181557

1519-
func (*mockEnv) Format(t time.Time) string { return t.Format(time.RFC822) }
1558+
func (*mockEnv) Format(t time.Time) string {
1559+
return t.Format(time.RFC822)
1560+
}
15201561

15211562
type ticket struct {
15221563
Price int
@@ -1573,37 +1614,3 @@ func (p *patcher) Exit(node *ast.Node) {
15731614
})
15741615
}
15751616
}
1576-
1577-
//
1578-
//var stringer = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
1579-
//
1580-
//type stringerPatcher struct{}
1581-
//
1582-
//func (p *stringerPatcher) Enter(_ *ast.Node) {}
1583-
//func (p *stringerPatcher) Exit(node *ast.Node) {
1584-
// t := (*node).Type()
1585-
// if t == nil {
1586-
// return
1587-
// }
1588-
// if t.Implements(stringer) {
1589-
// ast.Patch(node, &ast.MethodNode{
1590-
// Node: *node,
1591-
// Method: "String",
1592-
// })
1593-
// }
1594-
//}
1595-
//
1596-
//type lengthPatcher struct{}
1597-
//
1598-
//func (p *lengthPatcher) Enter(_ *ast.Node) {}
1599-
//func (p *lengthPatcher) Exit(node *ast.Node) {
1600-
// switch n := (*node).(type) {
1601-
// case *ast.MemberNode:
1602-
// if n.Property == "length" {
1603-
// ast.Patch(node, &ast.BuiltinNode{
1604-
// Name: "len",
1605-
// Arguments: []ast.Node{n.Node},
1606-
// })
1607-
// }
1608-
// }
1609-
//}

optimizer/optimizer_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ func TestOptimize_constant_folding(t *testing.T) {
2020
err = optimizer.Optimize(&tree.Node, nil)
2121
require.NoError(t, err)
2222

23-
expected := &ast.IndexNode{
24-
Node: &ast.ConstantNode{Value: []int{1, 2, 3}},
25-
Index: &ast.IntegerNode{Value: 0},
23+
expected := &ast.MemberNode{
24+
Node: &ast.ConstantNode{Value: []int{1, 2, 3}},
25+
Property: &ast.IntegerNode{Value: 0},
2626
}
2727

2828
assert.Equal(t, ast.Dump(expected), ast.Dump(tree.Node))

vm/runtime/runtime.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ func Fetch(from, i interface{}) interface{} {
2727
// Methods can be defined on any type.
2828
if v.NumMethod() > 0 {
2929
method := v.MethodByName(reflect.ValueOf(i).String())
30-
if method.IsValid() {
31-
return method
30+
if method.IsValid() && method.CanInterface() {
31+
return method.Interface()
3232
}
3333
}
3434

vm/vm.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ func (vm *VM) Run(program *Program, env interface{}) (out interface{}, err error
274274
vm.push(runtime.Fetch(a, b))
275275

276276
case OpCall:
277-
fn := vm.pop()
277+
fn := reflect.ValueOf(vm.pop())
278278
size := vm.arg()
279279
in := make([]reflect.Value, size)
280280
for i := int(size) - 1; i >= 0; i-- {
@@ -287,14 +287,14 @@ func (vm *VM) Run(program *Program, env interface{}) (out interface{}, err error
287287
in[i] = reflect.ValueOf(param)
288288
}
289289
}
290-
out := fn.(reflect.Value).Call(in)
290+
out := fn.Call(in)
291291
if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() {
292292
return nil, out[1].Interface().(error)
293293
}
294294
vm.push(out[0].Interface())
295295

296296
case OpCallFast:
297-
fn := vm.pop().(reflect.Value).Interface()
297+
fn := vm.pop()
298298
size := vm.arg()
299299
in := make([]interface{}, size)
300300
for i := int(size) - 1; i >= 0; i-- {

0 commit comments

Comments
 (0)