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:
|
case *ast.BinaryExpr:
|
||||||
switch n.Op {
|
switch n.Op {
|
||||||
case token.LAND:
|
case token.LAND:
|
||||||
|
next := c.newLabel()
|
||||||
|
end := c.newLabel()
|
||||||
ast.Walk(c, n.X)
|
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)
|
ast.Walk(c, n.Y)
|
||||||
|
c.setLabel(end)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case token.LOR:
|
case token.LOR:
|
||||||
|
next := c.newLabel()
|
||||||
|
end := c.newLabel()
|
||||||
ast.Walk(c, n.X)
|
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)
|
ast.Walk(c, n.Y)
|
||||||
|
c.setLabel(end)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package compiler_test
|
package compiler_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -704,3 +705,52 @@ func TestForLoopRangeCompilerError(t *testing.T) {
|
||||||
_, err := compiler.Compile(strings.NewReader(src))
|
_, err := compiler.Compile(strings.NewReader(src))
|
||||||
require.Error(t, err)
|
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