Skip to content

Commit e092ae3

Browse files
andrewbentonantonmedv
authored andcommitted
Adds simple bool condition optimization
This change introduces simple boolean condition optimization when dealing with boolean `and`, `or`, and `==` expressions to help trim unnecessary AST nodes.
1 parent 12758d7 commit e092ae3

File tree

3 files changed

+72
-3
lines changed

3 files changed

+72
-3
lines changed

compiler/compiler_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,12 @@ func TestCompile(t *testing.T) {
112112
{
113113
`-1`,
114114
vm.Program{
115-
Constants: []interface{}{-1},
115+
Constants: []interface{}{1},
116116
Bytecode: []vm.Opcode{
117117
vm.OpPush,
118+
vm.OpNegate,
118119
},
119-
Arguments: []int{0},
120+
Arguments: []int{0, 0},
120121
},
121122
},
122123
{
@@ -232,7 +233,7 @@ func TestCompile(t *testing.T) {
232233
}
233234

234235
for _, test := range tests {
235-
program, err := expr.Compile(test.input, expr.Env(Env{}))
236+
program, err := expr.Compile(test.input, expr.Env(Env{}), expr.Optimize(false))
236237
require.NoError(t, err, test.input)
237238

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

optimizer/fold.go

+56
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func (fold *fold) Visit(node *Node) {
4242
if i, ok := n.Node.(*FloatNode); ok {
4343
patchWithType(&FloatNode{Value: i.Value}, n.Node.Type())
4444
}
45+
case "!", "not":
46+
if a := toBool(n.Node); a != nil {
47+
patch(&BoolNode{Value: !a.Value})
48+
}
4549
}
4650

4751
case *BinaryNode:
@@ -211,6 +215,50 @@ func (fold *fold) Visit(node *Node) {
211215
patchWithType(&FloatNode{Value: math.Pow(a.Value, b.Value)}, a.Type())
212216
}
213217
}
218+
case "and", "&&":
219+
a := toBool(n.Left)
220+
b := toBool(n.Right)
221+
222+
if a != nil && a.Value { // true and x
223+
patch(n.Right)
224+
} else if b != nil && b.Value { // x and true
225+
patch(n.Left)
226+
} else if (a != nil && !a.Value) || (b != nil && !b.Value) { // "x and false" or "false and x"
227+
patch(&BoolNode{Value: false})
228+
}
229+
case "or", "||":
230+
a := toBool(n.Left)
231+
b := toBool(n.Right)
232+
233+
if a != nil && !a.Value { // false or x
234+
patch(n.Right)
235+
} else if b != nil && !b.Value { // x or false
236+
patch(n.Left)
237+
} else if (a != nil && a.Value) || (b != nil && b.Value) { // "x or true" or "true or x"
238+
patch(&BoolNode{Value: true})
239+
}
240+
case "==":
241+
{
242+
a := toInteger(n.Left)
243+
b := toInteger(n.Right)
244+
if a != nil && b != nil {
245+
patch(&BoolNode{Value: a.Value == b.Value})
246+
}
247+
}
248+
{
249+
a := toString(n.Left)
250+
b := toString(n.Right)
251+
if a != nil && b != nil {
252+
patch(&BoolNode{Value: a.Value == b.Value})
253+
}
254+
}
255+
{
256+
a := toBool(n.Left)
257+
b := toBool(n.Right)
258+
if a != nil && b != nil {
259+
patch(&BoolNode{Value: a.Value == b.Value})
260+
}
261+
}
214262
}
215263

216264
case *ArrayNode:
@@ -285,3 +333,11 @@ func toFloat(n Node) *FloatNode {
285333
}
286334
return nil
287335
}
336+
337+
func toBool(n Node) *BoolNode {
338+
switch a := n.(type) {
339+
case *BoolNode:
340+
return a
341+
}
342+
return nil
343+
}

optimizer/optimizer_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ func TestOptimize_constant_folding_with_floats(t *testing.T) {
4040
assert.Equal(t, ast.Dump(expected), ast.Dump(tree.Node))
4141
}
4242

43+
func TestOptimize_constant_folding_with_bools(t *testing.T) {
44+
tree, err := parser.Parse(`(true and false) or (true or false) or (false and false) or (true and (true == false))`)
45+
require.NoError(t, err)
46+
47+
err = optimizer.Optimize(&tree.Node, nil)
48+
require.NoError(t, err)
49+
50+
expected := &ast.BoolNode{Value: true}
51+
52+
assert.Equal(t, ast.Dump(expected), ast.Dump(tree.Node))
53+
}
54+
4355
func TestOptimize_in_array(t *testing.T) {
4456
config := conf.New(map[string]int{"v": 0})
4557

0 commit comments

Comments
 (0)