compiler: implement Remove builtin

Support removing items from collections via REMOVE opcode.
This commit is contained in:
Evgenii Stratonikov 2020-06-08 12:02:03 +03:00
parent 9a0d7d3254
commit 9f7f94a6fb
4 changed files with 70 additions and 0 deletions

View file

@ -17,6 +17,7 @@ var (
"VerifySignature", "AppCall", "VerifySignature", "AppCall",
"FromAddress", "Equals", "FromAddress", "Equals",
"panic", "DynAppCall", "panic", "DynAppCall",
"delete", "Remove",
} }
) )

View file

@ -1075,6 +1075,19 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
} else { } else {
c.prog.Err = errors.New("panic should have string or nil argument") c.prog.Err = errors.New("panic should have string or nil argument")
} }
case "delete", "Remove":
arg := expr.Args[0]
errNotSupported := errors.New("only maps and non-byte slices are supported in `Remove`")
switch typ := c.typeInfo.Types[arg].Type.Underlying().(type) {
case *types.Map:
case *types.Slice:
if isByte(typ.Elem()) {
c.prog.Err = errNotSupported
}
default:
c.prog.Err = errNotSupported
}
emit.Opcode(c.prog.BinWriter, opcode.REMOVE)
case "SHA256": case "SHA256":
emit.Opcode(c.prog.BinWriter, opcode.SHA256) emit.Opcode(c.prog.BinWriter, opcode.SHA256)
case "SHA1": case "SHA1":

View file

@ -2,6 +2,7 @@ package compiler_test
import ( import (
"fmt" "fmt"
"math/big"
"strings" "strings"
"testing" "testing"
@ -13,6 +14,56 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestRemove(t *testing.T) {
srcTmpl := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() int {
a := %s
util.Remove(a, %d)
return len(a) * a[%d]
}`
testRemove := func(item string, key, index, result int64) func(t *testing.T) {
return func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, item, key, index)
if result > 0 {
eval(t, src, big.NewInt(result))
return
}
v := vmAndCompile(t, src)
require.Error(t, v.Run())
}
}
t.Run("Map", func(t *testing.T) {
item := "map[int]int{1: 2, 5: 7, 11: 13}"
t.Run("RemovedKey", testRemove(item, 5, 5, -1))
t.Run("AnotherKey", testRemove(item, 5, 11, 26))
})
t.Run("Slice", func(t *testing.T) {
item := "[]int{5, 7, 11, 13}"
t.Run("RemovedKey", testRemove(item, 2, 2, 39))
t.Run("AnotherKey", testRemove(item, 2, 1, 21))
t.Run("LastKey", testRemove(item, 2, 3, -1))
})
t.Run("Invalid", func(t *testing.T) {
srcTmpl := `package foo
import "github.com/nspcc-dev/neo-go/pkg/interop/util"
func Main() int {
util.Remove(%s, 2)
return 1
}`
t.Run("BasicType", func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, "1")
_, err := compiler.Compile(strings.NewReader(src))
require.Error(t, err)
})
t.Run("ByteSlice", func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, "[]byte{1, 2}")
_, err := compiler.Compile(strings.NewReader(src))
require.Error(t, err)
})
})
}
func TestFromAddress(t *testing.T) { func TestFromAddress(t *testing.T) {
as1 := "Aej1fe4mUgou48Zzup5j8sPrE3973cJ5oz" as1 := "Aej1fe4mUgou48Zzup5j8sPrE3973cJ5oz"
addr1, err := address.StringToUint160(as1) addr1, err := address.StringToUint160(as1)

View file

@ -17,3 +17,8 @@ func FromAddress(address string) []byte {
func Equals(a, b interface{}) bool { func Equals(a, b interface{}) bool {
return false return false
} }
// Remove removes item with the specified key from slice or map.
// For maps it is similar to `delete`.
// For slices it performs mutable update as if slice was provided by pointer.
func Remove(sliceOrMap, key interface{}) {}