Skip to content

Commit f7bcbfa

Browse files
committed
Add var in a..b optimization
1 parent a70bd89 commit f7bcbfa

File tree

4 files changed

+108
-28
lines changed

4 files changed

+108
-28
lines changed

expr.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ func Optimize(b bool) conf.Option {
8080

8181
// Compile parses and compiles given input expression to bytecode program.
8282
func Compile(input string, ops ...conf.Option) (*vm.Program, error) {
83-
config := &conf.Config{Operators: make(map[string][]string)}
83+
config := &conf.Config{
84+
Operators: make(map[string][]string),
85+
Optimize: true,
86+
}
8487

8588
for _, op := range ops {
8689
op(config)
@@ -103,7 +106,9 @@ func Compile(input string, ops ...conf.Option) (*vm.Program, error) {
103106
checker.PatchOperators(tree, config)
104107
}
105108

106-
optimizer.Optimize(&tree.Node)
109+
if config.Optimize {
110+
optimizer.Optimize(&tree.Node)
111+
}
107112

108113
program, err := compiler.Compile(tree, config)
109114
if err != nil {

expr_test.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -448,25 +448,21 @@ func TestExpr(t *testing.T) {
448448
4,
449449
},
450450
{
451-
`[1, 2, 3]`,
452-
[]int{1, 2, 3},
451+
`len([1, 2, 3])`,
452+
3,
453453
},
454454
{
455-
`[1, Two, 3]`,
456-
[]interface{}{1, 2, 3},
455+
`len([1, Two, 3])`,
456+
3,
457457
},
458458
{
459-
`["hello", "world"]`,
460-
[]string{"hello", "world"},
459+
`len(["hello", "world"])`,
460+
2,
461461
},
462462
{
463463
`{foo: 0, bar: 1}`,
464464
map[string]interface{}{"foo": 0, "bar": 1},
465465
},
466-
{
467-
`[1, 2, 3]`,
468-
[]int{1, 2, 3},
469-
},
470466
{
471467
`{foo: 0, bar: 1}`,
472468
map[string]interface{}{"foo": 0, "bar": 1},
@@ -583,6 +579,10 @@ func TestExpr(t *testing.T) {
583579
`Array[:] == Array`,
584580
true,
585581
},
582+
{
583+
`One in 0..1 && Two not in 0..1`,
584+
true,
585+
},
586586
}
587587

588588
for _, tt := range tests {
@@ -594,6 +594,23 @@ func TestExpr(t *testing.T) {
594594

595595
assert.Equal(t, tt.want, got, tt.code)
596596
}
597+
598+
for _, tt := range tests {
599+
program, err := expr.Compile(tt.code, expr.Optimize(false))
600+
require.NoError(t, err, tt.code)
601+
602+
got, err := expr.Run(program, env)
603+
require.NoError(t, err, tt.code)
604+
605+
assert.Equal(t, tt.want, got, "unoptimized: "+tt.code)
606+
}
607+
608+
for _, tt := range tests {
609+
got, err := expr.Eval(tt.code, env)
610+
require.NoError(t, err, tt.code)
611+
612+
assert.Equal(t, tt.want, got, "eval: "+tt.code)
613+
}
597614
}
598615

599616
type mockEnv struct {

optimizer/optimizer.go

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,45 @@
11
package optimizer
22

33
import (
4-
"github.com/antonmedv/expr/ast"
4+
. "github.com/antonmedv/expr/ast"
55
)
66

7-
type optimizer struct {
8-
}
9-
10-
func (*optimizer) Enter(node *ast.Node) {}
7+
type fold struct{}
8+
type inRange struct{}
119

12-
func (*optimizer) Exit(node *ast.Node) {
10+
func (*fold) Enter(node *Node) {}
11+
func (*fold) Exit(node *Node) {
1312
switch n := (*node).(type) {
14-
case *ast.ArrayNode:
13+
case *ArrayNode:
1514
if len(n.Nodes) > 0 {
1615

1716
for _, a := range n.Nodes {
18-
if _, ok := a.(*ast.IntegerNode); !ok {
17+
if _, ok := a.(*IntegerNode); !ok {
1918
goto string
2019
}
2120
}
2221
{
2322
value := make([]int, len(n.Nodes))
2423
for i, a := range n.Nodes {
25-
value[i] = a.(*ast.IntegerNode).Value
24+
value[i] = a.(*IntegerNode).Value
2625
}
27-
*node = &ast.ConstantNode{
26+
*node = &ConstantNode{
2827
Value: value,
2928
}
3029
}
3130

3231
string:
3332
for _, a := range n.Nodes {
34-
if _, ok := a.(*ast.StringNode); !ok {
33+
if _, ok := a.(*StringNode); !ok {
3534
return
3635
}
3736
}
3837
{
3938
value := make([]string, len(n.Nodes))
4039
for i, a := range n.Nodes {
41-
value[i] = a.(*ast.StringNode).Value
40+
value[i] = a.(*StringNode).Value
4241
}
43-
*node = &ast.ConstantNode{
42+
*node = &ConstantNode{
4443
Value: value,
4544
}
4645
}
@@ -49,7 +48,41 @@ func (*optimizer) Exit(node *ast.Node) {
4948
}
5049
}
5150

52-
func Optimize(node *ast.Node) {
53-
o := &optimizer{}
54-
ast.Walk(node, o)
51+
func (*inRange) Enter(node *Node) {}
52+
func (*inRange) Exit(node *Node) {
53+
switch n := (*node).(type) {
54+
case *BinaryNode:
55+
if n.Operator == "in" || n.Operator == "not in" {
56+
if rng, ok := n.Right.(*BinaryNode); ok && rng.Operator == ".." {
57+
if from, ok := n.Left.(*IntegerNode); ok {
58+
if to, ok := n.Right.(*IntegerNode); ok {
59+
*node = &BinaryNode{
60+
Operator: "and",
61+
Left: &BinaryNode{
62+
Operator: ">=",
63+
Left: n.Left,
64+
Right: from,
65+
},
66+
Right: &BinaryNode{
67+
Operator: "<=",
68+
Left: n.Left,
69+
Right: to,
70+
},
71+
}
72+
if n.Operator == "not in" {
73+
*node = &UnaryNode{
74+
Operator: "not",
75+
Node: *node,
76+
}
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
}
84+
85+
func Optimize(node *Node) {
86+
Walk(node, &fold{})
87+
Walk(node, &inRange{})
5588
}

optimizer/optimizer_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,28 @@ func TestOptimize_constant_folding(t *testing.T) {
2424

2525
assert.Equal(t, litter.Sdump(expected), litter.Sdump(tree.Node))
2626
}
27+
28+
func TestOptimize_in_range(t *testing.T) {
29+
tree, err := parser.Parse(`age in 18..31`)
30+
require.NoError(t, err)
31+
32+
optimizer.Optimize(&tree.Node)
33+
34+
expected := &ast.BinaryNode{
35+
Operator: "in",
36+
Left: &ast.IdentifierNode{
37+
Value: "age",
38+
},
39+
Right: &ast.BinaryNode{
40+
Operator: "..",
41+
Left: &ast.IntegerNode{
42+
Value: 18,
43+
},
44+
Right: &ast.IntegerNode{
45+
Value: 31,
46+
},
47+
},
48+
}
49+
50+
assert.Equal(t, litter.Sdump(expected), litter.Sdump(tree.Node))
51+
}

0 commit comments

Comments
 (0)