From 3d4854f96813dbc0a0541540155b7515acc123e5 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Wed, 20 Sep 2023 22:58:04 +0530 Subject: [PATCH 1/4] Introduce String.split function --- runtime/interpreter/value.go | 59 ++++++++++++++ runtime/sema/string_type.go | 26 +++++++ runtime/tests/checker/string_test.go | 43 +++++++++++ runtime/tests/interpreter/string_test.go | 97 ++++++++++++++++++++++++ 4 files changed, 225 insertions(+) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index b80d96da8e..d0c61e65fe 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -1043,6 +1043,8 @@ var _ ValueIndexableValue = &StringValue{} var _ MemberAccessibleValue = &StringValue{} var _ IterableValue = &StringValue{} +var VarSizedArrayOfStringType = NewVariableSizedStaticType(nil, PrimitiveStaticTypeString) + func (v *StringValue) prepareGraphemes() { if v.graphemes == nil { v.graphemes = uniseg.NewGraphemes(v.Str) @@ -1342,6 +1344,15 @@ func (v *StringValue) GetMember(interpreter *Interpreter, locationRange Location return v.ToLower(invocation.Interpreter) }, ) + + case sema.StringTypeSplitFunctionName: + return NewHostFunctionValue( + interpreter, + sema.StringTypeSplitFunctionType, + func(invocation Invocation) Value { + return v.Split(invocation) + }, + ) } return nil @@ -1396,6 +1407,54 @@ func (v *StringValue) ToLower(interpreter *Interpreter) *StringValue { ) } +func (v *StringValue) Split(invocation Invocation) Value { + inter := invocation.Interpreter + + separator, ok := invocation.Arguments[0].(*StringValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + split := strings.Split(v.Str, separator.Str) + + var index int + count := len(split) + + return NewArrayValueWithIterator( + inter, + VarSizedArrayOfStringType, + common.ZeroAddress, + uint64(count), + func() Value { + if index >= count { + return nil + } + + str := split[index] + strValue := NewStringValue( + inter, + common.NewStringMemoryUsage(len(str)), + func() string { + return str + }, + ) + + index++ + + value := strValue.Transfer( + inter, + invocation.LocationRange, + atree.Address(common.ZeroAddress), + true, + nil, + nil, + ) + + return value + }, + ) +} + func (v *StringValue) Storable(storage atree.SlabStorage, address atree.Address, maxInlineSize uint64) (atree.Storable, error) { return maybeLargeImmutableStorable(v, storage, address, maxInlineSize) } diff --git a/runtime/sema/string_type.go b/runtime/sema/string_type.go index b4fa762599..691f14db13 100644 --- a/runtime/sema/string_type.go +++ b/runtime/sema/string_type.go @@ -42,6 +42,11 @@ const StringTypeJoinFunctionDocString = ` Returns a string after joining the array of strings with the provided separator. ` +const StringTypeSplitFunctionName = "split" +const StringTypeSplitFunctionDocString = ` +Returns a variable-sized array of strings after splitting the string on the delimiter. +` + // StringType represents the string type var StringType = &SimpleType{ Name: "String", @@ -105,6 +110,12 @@ func init() { StringTypeToLowerFunctionType, stringTypeToLowerFunctionDocString, ), + NewUnmeteredPublicFunctionMember( + t, + StringTypeSplitFunctionName, + StringTypeSplitFunctionType, + StringTypeSplitFunctionDocString, + ), }) } } @@ -335,3 +346,18 @@ var StringTypeJoinFunctionType = NewSimpleFunctionType( }, StringTypeAnnotation, ) + +var StringTypeSplitFunctionType = NewSimpleFunctionType( + FunctionPurityView, + []Parameter{ + { + Identifier: "separator", + TypeAnnotation: StringTypeAnnotation, + }, + }, + NewTypeAnnotation( + &VariableSizedType{ + Type: StringType, + }, + ), +) diff --git a/runtime/tests/checker/string_test.go b/runtime/tests/checker/string_test.go index 2ce9fc681b..5d94c0c077 100644 --- a/runtime/tests/checker/string_test.go +++ b/runtime/tests/checker/string_test.go @@ -408,3 +408,46 @@ func TestCheckStringJoinTypeMissingArgumentLabelSeparator(t *testing.T) { assert.IsType(t, &sema.MissingArgumentLabelError{}, errs[0]) } + +func TestCheckStringSplit(t *testing.T) { + + t.Parallel() + + checker, err := ParseAndCheck(t, ` + let s = "👪.❤️.Abc".split(separator: ".") + `) + require.NoError(t, err) + + assert.Equal(t, + &sema.VariableSizedType{ + Type: sema.StringType, + }, + RequireGlobalValue(t, checker.Elaboration, "s"), + ) +} + +func TestCheckStringSplitTypeMismatchSeparator(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + let s = "Abc:1".split(separator: 1234) + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.TypeMismatchError{}, errs[0]) +} + +func TestCheckStringSplitTypeMissingArgumentLabelSeparator(t *testing.T) { + + t.Parallel() + + _, err := ParseAndCheck(t, ` + let s = "👪Abc".split("/") + `) + + errs := RequireCheckerErrors(t, err, 1) + + assert.IsType(t, &sema.MissingArgumentLabelError{}, errs[0]) +} diff --git a/runtime/tests/interpreter/string_test.go b/runtime/tests/interpreter/string_test.go index 51873a5a79..37bba83186 100644 --- a/runtime/tests/interpreter/string_test.go +++ b/runtime/tests/interpreter/string_test.go @@ -499,3 +499,100 @@ func TestInterpretStringJoin(t *testing.T) { testCase(t, "testEmptyArray", interpreter.NewUnmeteredStringValue("")) testCase(t, "testSingletonArray", interpreter.NewUnmeteredStringValue("pqrS")) } + +func TestInterpretStringSplit(t *testing.T) { + + t.Parallel() + + inter := parseCheckAndInterpret(t, ` + fun split(): [String] { + return "👪////❤️".split(separator: "////") + } + fun splitBySpace(): [String] { + return "👪 ❤️ Abc6 ;123".split(separator: " ") + } + fun splitWithUnicodeEquivalence(): [String] { + return "Caf\u{65}\u{301}ABc".split(separator: "\u{e9}") + } + fun testEmptyString(): [String] { + return "".split(separator: "//") + } + fun testNoMatch(): [String] { + return "pqrS;asdf".split(separator: ";;") + } + `) + + testCase := func(t *testing.T, funcName string, expected *interpreter.ArrayValue) { + t.Run(funcName, func(t *testing.T) { + result, err := inter.Invoke(funcName) + require.NoError(t, err) + + RequireValuesEqual( + t, + inter, + expected, + result, + ) + }) + } + + varSizedStringType := &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeString, + } + + testCase(t, + "split", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + varSizedStringType, + common.ZeroAddress, + interpreter.NewUnmeteredStringValue("👪"), + interpreter.NewUnmeteredStringValue("❤️"), + ), + ) + testCase(t, + "splitBySpace", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + varSizedStringType, + common.ZeroAddress, + interpreter.NewUnmeteredStringValue("👪"), + interpreter.NewUnmeteredStringValue("❤️"), + interpreter.NewUnmeteredStringValue("Abc6"), + interpreter.NewUnmeteredStringValue(";123"), + ), + ) + testCase(t, + "splitWithUnicodeEquivalence", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + varSizedStringType, + common.ZeroAddress, + interpreter.NewUnmeteredStringValue("Caf"), + interpreter.NewUnmeteredStringValue("ABc"), + ), + ) + testCase(t, + "testEmptyString", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + varSizedStringType, + common.ZeroAddress, + interpreter.NewUnmeteredStringValue(""), + ), + ) + testCase(t, + "testNoMatch", + interpreter.NewArrayValue( + inter, + interpreter.EmptyLocationRange, + varSizedStringType, + common.ZeroAddress, + interpreter.NewUnmeteredStringValue("pqrS;asdf"), + ), + ) +} From cd2d26c11dd3329e24c1cbf664c63d2fd443046f Mon Sep 17 00:00:00 2001 From: darkdrag00nv2 <122124396+darkdrag00nv2@users.noreply.github.com> Date: Thu, 21 Sep 2023 22:24:32 +0530 Subject: [PATCH 2/4] Update runtime/interpreter/value.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bastian Müller --- runtime/interpreter/value.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index d0c61e65fe..c005d03c4d 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -1444,7 +1444,7 @@ func (v *StringValue) Split(invocation Invocation) Value { value := strValue.Transfer( inter, invocation.LocationRange, - atree.Address(common.ZeroAddress), + atree.Address{}, true, nil, nil, From 839556c69d6c5a78a0825207aab3acc183ff07fe Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Thu, 21 Sep 2023 22:28:35 +0530 Subject: [PATCH 3/4] pass interpreter, location range and separator to Split function --- runtime/interpreter/value.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index c005d03c4d..b53445b696 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -1350,7 +1350,12 @@ func (v *StringValue) GetMember(interpreter *Interpreter, locationRange Location interpreter, sema.StringTypeSplitFunctionType, func(invocation Invocation) Value { - return v.Split(invocation) + separator, ok := invocation.Arguments[0].(*StringValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + return v.Split(invocation.Interpreter, invocation.LocationRange, separator.Str) }, ) } @@ -1407,15 +1412,8 @@ func (v *StringValue) ToLower(interpreter *Interpreter) *StringValue { ) } -func (v *StringValue) Split(invocation Invocation) Value { - inter := invocation.Interpreter - - separator, ok := invocation.Arguments[0].(*StringValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - split := strings.Split(v.Str, separator.Str) +func (v *StringValue) Split(inter *Interpreter, locationRange LocationRange, separator string) Value { + split := strings.Split(v.Str, separator) var index int count := len(split) @@ -1443,7 +1441,7 @@ func (v *StringValue) Split(invocation Invocation) Value { value := strValue.Transfer( inter, - invocation.LocationRange, + locationRange, atree.Address{}, true, nil, From e7a7725aab0f4686611bb61dc965eabb6902b6e1 Mon Sep 17 00:00:00 2001 From: darkdrag00n Date: Sun, 24 Sep 2023 20:26:46 +0530 Subject: [PATCH 4/4] Remove unnecessary transfer call --- runtime/interpreter/value.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/runtime/interpreter/value.go b/runtime/interpreter/value.go index b53445b696..e1a60855df 100644 --- a/runtime/interpreter/value.go +++ b/runtime/interpreter/value.go @@ -1429,26 +1429,14 @@ func (v *StringValue) Split(inter *Interpreter, locationRange LocationRange, sep } str := split[index] - strValue := NewStringValue( + index++ + return NewStringValue( inter, common.NewStringMemoryUsage(len(str)), func() string { return str }, ) - - index++ - - value := strValue.Transfer( - inter, - locationRange, - atree.Address{}, - true, - nil, - nil, - ) - - return value }, ) }