forked from TrueCloudLab/neoneo-go
compiler: do not short-circuit in complex conditions
Current implementation of short-circuting is just plain wrong as it uses `last` or `before-last` labels which meaning depend on context. It doesn't even handle simple assignements like `a := x == 1 && y == 2`. This commit makes all jumps in such conditions local and adds tests. Closes #699, #700.
This commit is contained in:
parent
26c4e83ddf
commit
b461a6ab63
2 changed files with 64 additions and 2 deletions
|
@ -505,15 +505,27 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
case *ast.BinaryExpr:
|
||||
switch n.Op {
|
||||
case token.LAND:
|
||||
next := c.newLabel()
|
||||
end := c.newLabel()
|
||||
ast.Walk(c, n.X)
|
||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, uint16(len(c.l)-1))
|
||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, next)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHF)
|
||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, end)
|
||||
c.setLabel(next)
|
||||
ast.Walk(c, n.Y)
|
||||
c.setLabel(end)
|
||||
return nil
|
||||
|
||||
case token.LOR:
|
||||
next := c.newLabel()
|
||||
end := c.newLabel()
|
||||
ast.Walk(c, n.X)
|
||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIF, uint16(len(c.l)-3))
|
||||
emit.Jmp(c.prog.BinWriter, opcode.JMPIFNOT, next)
|
||||
emit.Opcode(c.prog.BinWriter, opcode.PUSHT)
|
||||
emit.Jmp(c.prog.BinWriter, opcode.JMP, end)
|
||||
c.setLabel(next)
|
||||
ast.Walk(c, n.Y)
|
||||
c.setLabel(end)
|
||||
return nil
|
||||
|
||||
default:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package compiler_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -704,3 +705,52 @@ func TestForLoopRangeCompilerError(t *testing.T) {
|
|||
_, err := compiler.Compile(strings.NewReader(src))
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestForLoopComplexConditions(t *testing.T) {
|
||||
src := `
|
||||
package foo
|
||||
func Main() int {
|
||||
var ok bool
|
||||
_ = ok
|
||||
i := 0
|
||||
j := 0
|
||||
%s
|
||||
for %s {
|
||||
i++
|
||||
j++
|
||||
%s
|
||||
}
|
||||
return i
|
||||
}`
|
||||
|
||||
tests := []struct {
|
||||
Name string
|
||||
Cond string
|
||||
Assign string
|
||||
Result int64
|
||||
}{
|
||||
{Cond: "i < 3 && j < 2", Result: 2},
|
||||
{Cond: "i < 3 || j < 2", Result: 3},
|
||||
{Cond: "i < 3 && (j < 2 || i < 1)", Result: 2},
|
||||
{Cond: "i < 3 && (j < 2 && i < 1)", Result: 1},
|
||||
{Cond: "(i < 1 || j < 3) && (i < 3 || j < 1)", Result: 3},
|
||||
{Cond: "(i < 2 && j < 4) || (i < 4 && j < 2)", Result: 2},
|
||||
{Cond: "ok", Assign: "ok = i < 3 && j < 2", Result: 2},
|
||||
{Cond: "ok", Assign: "ok = i < 3 || j < 2", Result: 3},
|
||||
{Cond: "ok", Assign: "ok = i < 3 && (j < 2 || i < 1)", Result: 2},
|
||||
{Cond: "ok", Assign: "ok = i < 3 && (j < 2 && i < 1)", Result: 1},
|
||||
{Cond: "ok", Assign: "ok = (i < 1 || j < 3) && (i < 3 || j < 1)", Result: 3},
|
||||
{Cond: "ok", Assign: "ok = (i < 2 && j < 4) || (i < 4 && j < 2)", Result: 2},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
name := tc.Cond
|
||||
if tc.Assign != "" {
|
||||
name = tc.Assign
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
s := fmt.Sprintf(src, tc.Assign, tc.Cond, tc.Assign)
|
||||
eval(t, s, big.NewInt(tc.Result))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue