native/std: add stringSplit method

This commit is contained in:
Evgeniy Stratonikov 2021-04-30 17:00:29 +03:00
parent dadfe2b9ab
commit 40d1dd0e0d
4 changed files with 108 additions and 0 deletions

View file

@ -232,6 +232,8 @@ func TestNativeHelpersCompile(t *testing.T) {
{"memorySearch", []string{"[]byte{1}", "[]byte{2}"}}, {"memorySearch", []string{"[]byte{1}", "[]byte{2}"}},
{"memorySearchIndex", []string{"[]byte{1}", "[]byte{2}", "3"}}, {"memorySearchIndex", []string{"[]byte{1}", "[]byte{2}", "3"}},
{"memorySearchLastIndex", []string{"[]byte{1}", "[]byte{2}", "3"}}, {"memorySearchLastIndex", []string{"[]byte{1}", "[]byte{2}", "3"}},
{"stringSplit", []string{`"a,b"`, `","`}},
{"stringSplitNonEmpty", []string{`"a,b"`, `","`}},
}) })
} }
@ -256,6 +258,11 @@ func getMethod(t *testing.T, ctr interop.ContractMD, name string, params []strin
paramLen += 1 // true should be appended inside of an interop paramLen += 1 // true should be appended inside of an interop
} }
name = "memorySearch" name = "memorySearch"
case strings.HasPrefix(name, "stringSplit"):
if strings.HasSuffix(name, "NonEmpty") {
paramLen += 1 // true should be appended inside of an interop
}
name = "stringSplit"
default: default:
name = strings.TrimSuffix(name, "WithData") name = strings.TrimSuffix(name, "WithData")
} }

View file

@ -132,6 +132,19 @@ func newStd() *Std {
md = newMethodAndPrice(s.memorySearch4, 1<<6, callflag.NoneFlag) md = newMethodAndPrice(s.memorySearch4, 1<<6, callflag.NoneFlag)
s.AddMethod(md, desc) s.AddMethod(md, desc)
desc = newDescriptor("stringSplit", smartcontract.ArrayType,
manifest.NewParameter("str", smartcontract.StringType),
manifest.NewParameter("separator", smartcontract.StringType))
md = newMethodAndPrice(s.stringSplit2, 1<<8, callflag.NoneFlag)
s.AddMethod(md, desc)
desc = newDescriptor("stringSplit", smartcontract.ArrayType,
manifest.NewParameter("str", smartcontract.StringType),
manifest.NewParameter("separator", smartcontract.StringType),
manifest.NewParameter("removeEmptyEntries", smartcontract.BoolType))
md = newMethodAndPrice(s.stringSplit3, 1<<8, callflag.NoneFlag)
s.AddMethod(md, desc)
return s return s
} }
@ -355,6 +368,36 @@ func (s *Std) memorySearchAux(mem, val []byte, start int, backward bool) int {
return index + start return index + start
} }
func (s *Std) stringSplit2(_ *interop.Context, args []stackitem.Item) stackitem.Item {
str := s.toLimitedString(args[0])
sep := toString(args[1])
return stackitem.NewArray(s.stringSplitAux(str, sep, false))
}
func (s *Std) stringSplit3(_ *interop.Context, args []stackitem.Item) stackitem.Item {
str := s.toLimitedString(args[0])
sep := toString(args[1])
removeEmpty, err := args[2].TryBool()
if err != nil {
panic(err)
}
return stackitem.NewArray(s.stringSplitAux(str, sep, removeEmpty))
}
func (s *Std) stringSplitAux(str, sep string, removeEmpty bool) []stackitem.Item {
var result []stackitem.Item
arr := strings.Split(str, sep)
for i := range arr {
if !removeEmpty || len(arr[i]) != 0 {
result = append(result, stackitem.Make(arr[i]))
}
}
return result
}
// Metadata implements Contract interface. // Metadata implements Contract interface.
func (s *Std) Metadata() *interop.ContractMD { func (s *Std) Metadata() *interop.ContractMD {
return &s.ContractMD return &s.ContractMD

View file

@ -481,3 +481,47 @@ func TestMemorySearch(t *testing.T) {
func() { s.memorySearch4(ic, []stackitem.Item{s2, s1, start, b}) }) func() { s.memorySearch4(ic, []stackitem.Item{s2, s1, start, b}) })
}) })
} }
func TestStringSplit(t *testing.T) {
s := newStd()
ic := &interop.Context{VM: vm.New()}
check := func(t *testing.T, result []string, str, sep string, remove interface{}) {
args := []stackitem.Item{stackitem.Make(str), stackitem.Make(sep)}
var actual stackitem.Item
if remove == nil {
actual = s.stringSplit2(ic, args)
} else {
args = append(args, stackitem.NewBool(remove.(bool)))
actual = s.stringSplit3(ic, args)
}
arr, ok := actual.Value().([]stackitem.Item)
require.True(t, ok)
require.Equal(t, len(result), len(arr))
for i := range result {
require.Equal(t, stackitem.Make(result[i]), arr[i])
}
}
check(t, []string{"a", "b", "c"}, "abc", "", nil)
check(t, []string{"a", "b", "c"}, "abc", "", true)
check(t, []string{"a", "c", "", "", "d"}, "abcbbbd", "b", nil)
check(t, []string{"a", "c", "", "", "d"}, "abcbbbd", "b", false)
check(t, []string{"a", "c", "d"}, "abcbbbd", "b", true)
check(t, []string{""}, "", "abc", nil)
check(t, []string{}, "", "abc", true)
t.Run("C# compatibility", func(t *testing.T) {
// These tests are taken from C# node.
check(t, []string{"a", "b"}, "a,b", ",", nil)
})
t.Run("big arguments", func(t *testing.T) {
s1 := stackitem.Make(strings.Repeat("x", stdMaxInputLength+1))
s2 := stackitem.Make("xxx")
require.PanicsWithError(t, ErrTooBigInput.Error(),
func() { s.stringSplit2(ic, []stackitem.Item{s1, s2}) })
})
}

View file

@ -142,3 +142,17 @@ func MemorySearchLastIndex(mem, pattern []byte, start int) int {
return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag, return contract.Call(interop.Hash160(Hash), "memorySearch", contract.NoneFlag,
mem, pattern, start, true).(int) mem, pattern, start, true).(int)
} }
// StringSplit splits s by occurrences of sep.
// It uses `stringSplit` method of StdLib native contract.
func StringSplit(s, sep string) []string {
return contract.Call(interop.Hash160(Hash), "stringSplit", contract.NoneFlag,
s, sep).([]string)
}
// StringSplitNonEmpty splits s by occurrences of sep and returns a list of non-empty items.
// It uses `stringSplit` method of StdLib native contract.
func StringSplitNonEmpty(s, sep string) []string {
return contract.Call(interop.Hash160(Hash), "stringSplit", contract.NoneFlag,
s, sep, true).([]string)
}