Skip to content

Commit

Permalink
Merge pull request #2791 from darkdrag00nv2/string_split2
Browse files Browse the repository at this point in the history
Introduce `String.split` function
  • Loading branch information
SupunS authored Sep 25, 2023
2 parents 8696c7f + e7a7725 commit fa75807
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 0 deletions.
45 changes: 45 additions & 0 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1342,6 +1344,20 @@ 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 {
separator, ok := invocation.Arguments[0].(*StringValue)
if !ok {
panic(errors.NewUnreachableError())
}

return v.Split(invocation.Interpreter, invocation.LocationRange, separator.Str)
},
)
}

return nil
Expand Down Expand Up @@ -1396,6 +1412,35 @@ func (v *StringValue) ToLower(interpreter *Interpreter) *StringValue {
)
}

func (v *StringValue) Split(inter *Interpreter, locationRange LocationRange, separator string) Value {
split := strings.Split(v.Str, separator)

var index int
count := len(split)

return NewArrayValueWithIterator(
inter,
VarSizedArrayOfStringType,
common.ZeroAddress,
uint64(count),
func() Value {
if index >= count {
return nil
}

str := split[index]
index++
return NewStringValue(
inter,
common.NewStringMemoryUsage(len(str)),
func() string {
return str
},
)
},
)
}

func (v *StringValue) Storable(storage atree.SlabStorage, address atree.Address, maxInlineSize uint64) (atree.Storable, error) {
return maybeLargeImmutableStorable(v, storage, address, maxInlineSize)
}
Expand Down
26 changes: 26 additions & 0 deletions runtime/sema/string_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -105,6 +110,12 @@ func init() {
StringTypeToLowerFunctionType,
stringTypeToLowerFunctionDocString,
),
NewUnmeteredPublicFunctionMember(
t,
StringTypeSplitFunctionName,
StringTypeSplitFunctionType,
StringTypeSplitFunctionDocString,
),
})
}
}
Expand Down Expand Up @@ -335,3 +346,18 @@ var StringTypeJoinFunctionType = NewSimpleFunctionType(
},
StringTypeAnnotation,
)

var StringTypeSplitFunctionType = NewSimpleFunctionType(
FunctionPurityView,
[]Parameter{
{
Identifier: "separator",
TypeAnnotation: StringTypeAnnotation,
},
},
NewTypeAnnotation(
&VariableSizedType{
Type: StringType,
},
),
)
43 changes: 43 additions & 0 deletions runtime/tests/checker/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
97 changes: 97 additions & 0 deletions runtime/tests/interpreter/string_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
),
)
}

0 comments on commit fa75807

Please sign in to comment.