diff --git a/docs/compiler.md b/docs/compiler.md index 79069be64..ebee25c9d 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -32,6 +32,9 @@ pkg.go.dev](https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/interop) for full API documentation. In general it provides the same level of functionality as Neo .net Framework library. +Compiler provides some helpful builtins in `util` and `convert` packages. +Refer to them for detailed documentation. + ## Quick start ### Compiling diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index cd9e9148d..0dcc3d262 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -17,7 +17,7 @@ var ( goBuiltins = []string{"len", "append", "panic", "make", "copy", "recover", "delete"} // Custom builtin utility functions. customBuiltins = []string{ - "FromAddress", "Equals", + "FromAddress", "Equals", "Remove", "ToBool", "ToByteArray", "ToInteger", } ) diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index be9e8ec2c..8ce385f12 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -517,6 +517,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return nil case *ast.SliceExpr: + if isCompoundSlice(c.typeOf(n.X.(*ast.Ident)).Underlying()) { + c.prog.Err = errors.New("subslices are supported only for []byte") + return nil + } name := n.X.(*ast.Ident).Name c.emitLoadVar("", name) @@ -1538,6 +1542,12 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { typ = stackitem.BooleanT } c.emitConvert(typ) + case "Remove": + if !isCompoundSlice(c.typeOf(expr.Args[0])) { + c.prog.Err = errors.New("`Remove` supports only non-byte slices") + return + } + emit.Opcode(c.prog.BinWriter, opcode.REMOVE) case "Equals": emit.Opcode(c.prog.BinWriter, opcode.EQUAL) case "FromAddress": diff --git a/pkg/compiler/slice_test.go b/pkg/compiler/slice_test.go index ee776407f..e4bc8c6e9 100644 --- a/pkg/compiler/slice_test.go +++ b/pkg/compiler/slice_test.go @@ -316,6 +316,41 @@ func TestSliceOperations(t *testing.T) { runTestCases(t, sliceTestCases) } +func TestSubsliceCompound(t *testing.T) { + src := `package foo + func Main() []int { + a := []int{0, 1, 2, 3} + b := a[1:3] + return b + }` + _, err := compiler.Compile("", strings.NewReader(src)) + require.Error(t, err) +} + +func TestRemove(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/util" + func Main() int { + a := []int{11, 22, 33} + util.Remove(a, 1) + return len(a) + a[0] + a[1] + }` + eval(t, src, big.NewInt(46)) + }) + t.Run("ByteSlice", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/util" + func Main() int { + a := []byte{11, 22, 33} + util.Remove(a, 1) + return len(a) + }` + _, err := compiler.Compile("", strings.NewReader(src)) + require.Error(t, err) + }) +} + func TestJumps(t *testing.T) { src := ` package foo diff --git a/pkg/interop/util/util.go b/pkg/interop/util/util.go index 3e80a77b0..030918f43 100644 --- a/pkg/interop/util/util.go +++ b/pkg/interop/util/util.go @@ -17,3 +17,8 @@ func FromAddress(address string) []byte { func Equals(a, b interface{}) bool { return false } + +// Remove removes element with index i from slice. +// This is done in place and slice must have type other than `[]byte`. +func Remove(slice interface{}, i int) { +}