Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2da6058
Added initial implementation of SpanSplitEnumerator
bbartels Nov 26, 2019
e795f69
Merged SpanSplitEnumerator and SpanSplitSequenceEnumerator
bbartels May 22, 2020
0bc1a96
Reordered .projitems entry
bbartels May 22, 2020
addae39
Exposed System.Memory API additions
bbartels May 22, 2020
d153ebd
Added SpanSplitEnumerator Tests
bbartels May 25, 2020
3a5b758
Apply suggestions from code review
bbartels May 28, 2020
ebed669
Apply suggestions from code review
bbartels May 28, 2020
5c5b463
Moved SpanSplitEnumerator to separate file
bbartels May 28, 2020
97a2a44
Applied feedback to ReadOnlySpan.Split tests
bbartels May 28, 2020
549c41b
Renamed parameters/fields
bbartels May 28, 2020
a942c87
Merged upstream changes
bbartels May 28, 2020
4c8c6ac
Removed mistaken compile include
bbartels May 28, 2020
ea512e4
Fixed incorrect namespace on GetEnumeartor() return type
bbartels May 30, 2020
af9ae45
Applied feedback for ReadOnlySpan.Split tests
bbartels May 30, 2020
9534d08
Added XML Documentation to public members
bbartels May 31, 2020
be144a6
Fixed cref reference to SpanSplitEnumerator in XML Documentation
bbartels May 31, 2020
a906b39
Fixed System.Memory.cs entries missing fully qualified identifier
bbartels Jun 2, 2020
02520e1
Merge branch 'master' of https://github.com/dotnet/runtime
bbartels Jun 2, 2020
d42365c
Apply suggestions from code review
bbartels Jun 3, 2020
3eb0220
Merge branch 'master' of https://github.com/bbartels/runtime
bbartels Jun 3, 2020
20486ab
Moved SpanSplit Tests to correct file
bbartels Jun 3, 2020
443dad6
Applied review feedback
bbartels Jun 4, 2020
dab7335
Removed trailing whitespace
bbartels Jun 4, 2020
be90021
Fixed Unit Tests
bbartels Jun 4, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ public static void Sort<T>(this System.Span<T> span, System.Comparison<T> compar
public static void Sort<TKey, TValue>(this System.Span<TKey> keys, System.Span<TValue> items) { }
public static void Sort<TKey, TValue, TComparer>(this System.Span<TKey> keys, System.Span<TValue> items, TComparer comparer) where TComparer : System.Collections.Generic.IComparer<TKey>? { }
public static void Sort<TKey, TValue>(this System.Span<TKey> keys, System.Span<TValue> items, System.Comparison<TKey> comparison) { }
public static System.SpanSplitEnumerator<char> Split(this System.ReadOnlySpan<char> span) { throw null; }
public static System.SpanSplitEnumerator<char> Split(this System.ReadOnlySpan<char> span, char separator) { throw null; }
public static System.SpanSplitEnumerator<char> Split(this System.ReadOnlySpan<char> span, string separator) { throw null; }
public static bool StartsWith(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> value, System.StringComparison comparisonType) { throw null; }
public static bool StartsWith<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
public static bool StartsWith<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
Expand Down Expand Up @@ -156,6 +159,12 @@ public static void Sort<TKey, TValue>(this System.Span<TKey> keys, System.Span<T
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public object? GetObject() { throw null; }
}
public ref struct SpanSplitEnumerator<T> where T : System.IEquatable<T>
{
public System.SpanSplitEnumerator<T> GetEnumerator() { throw null; }
public readonly System.Range Current { get { throw null; } }
public bool MoveNext() { throw null; }
}
}
namespace System.Buffers
{
Expand Down
260 changes: 260 additions & 0 deletions src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Linq;
using Xunit;

namespace System.SpanTests
{
public static partial class ReadOnlySpanTests
{
[Fact]
public static void SplitNoMatchSingleResult()
{
ReadOnlySpan<char> value = "a b";

string expected = value.ToString();
var enumerator = value.Split(',');
Assert.True(enumerator.MoveNext());
Assert.Equal(expected, value[enumerator.Current].ToString());
}

[Fact]
public static void DefaultSpanSplitEnumeratorBehavior()
{
var charSpanEnumerator = new SpanSplitEnumerator<string>();
Assert.Equal(new Range(0, 0), charSpanEnumerator.Current);
Assert.False(charSpanEnumerator.MoveNext());

// Implicit DoesNotThrow assertion
charSpanEnumerator.GetEnumerator();

var stringSpanEnumerator = new SpanSplitEnumerator<string>();
Assert.Equal(new Range(0, 0), stringSpanEnumerator.Current);
Assert.False(stringSpanEnumerator.MoveNext());
stringSpanEnumerator.GetEnumerator();
}

[Fact]
public static void ValidateArguments_OverloadWithoutSeparator()
{
ReadOnlySpan<char> buffer = default;

SpanSplitEnumerator<char> enumerator = buffer.Split();
Assert.True(enumerator.MoveNext());
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.False(enumerator.MoveNext());

buffer = "";
enumerator = buffer.Split();
Assert.True(enumerator.MoveNext());
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.False(enumerator.MoveNext());

buffer = " ";
enumerator = buffer.Split();
Assert.True(enumerator.MoveNext());
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new Range(1, 1), enumerator.Current);
Assert.False(enumerator.MoveNext());
}

[Fact]
public static void ValidateArguments_OverloadWithROSSeparator()
{
// Default buffer
ReadOnlySpan<char> buffer = default;

SpanSplitEnumerator<char> enumerator = buffer.Split(default(char));
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split(' ');
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

// Empty buffer
buffer = "";

enumerator = buffer.Split(default(char));
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split(' ');
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

// Single whitespace buffer
buffer = " ";

enumerator = buffer.Split(default(char));
Assert.True(enumerator.MoveNext());
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split(' ');
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new Range(0, 0), enumerator.Current);
Assert.True(enumerator.MoveNext());
Assert.Equal(new Range(1, 1), enumerator.Current);
Assert.False(enumerator.MoveNext());
}

[Fact]
public static void ValidateArguments_OverloadWithStringSeparator()
{
// Default buffer
ReadOnlySpan<char> buffer = default;

SpanSplitEnumerator<char> enumerator = buffer.Split(null); // null is treated as empty string
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split("");
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split(" ");
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

// Empty buffer
buffer = "";

enumerator = buffer.Split(null);
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split("");
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split(" ");
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.False(enumerator.MoveNext());

// Single whitespace buffer
buffer = " ";

enumerator = buffer.Split(null); // null is treated as empty string
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(1, 1));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split("");
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(1, 1));
Assert.False(enumerator.MoveNext());

enumerator = buffer.Split(" ");
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(0, 0));
Assert.True(enumerator.MoveNext());
Assert.Equal(enumerator.Current, new Range(1, 1));
Assert.False(enumerator.MoveNext());
}

[Theory]
[InlineData("", ',', new[] { "" })]
[InlineData(" ", ' ', new[] { "", "" })]
[InlineData(",", ',', new[] { "", "" })]
[InlineData(" ", ' ', new[] { "", "", "", "", "", "" })]
[InlineData(",,", ',', new[] { "", "", "" })]
[InlineData("ab", ',', new[] { "ab" })]
[InlineData("a,b", ',', new[] { "a", "b" })]
[InlineData("a,", ',', new[] { "a", "" })]
[InlineData(",b", ',', new[] { "", "b" })]
[InlineData(",a,b", ',', new[] { "", "a", "b" })]
[InlineData("a,b,", ',', new[] { "a", "b", "" })]
[InlineData("a,b,c", ',', new[] { "a", "b", "c" })]
[InlineData("a,,c", ',', new[] { "a", "", "c" })]
[InlineData(",a,b,c", ',', new[] { "", "a", "b", "c" })]
[InlineData("a,b,c,", ',', new[] { "a", "b", "c", "" })]
[InlineData(",a,b,c,", ',', new[] { "", "a", "b", "c", "" })]
[InlineData("first,second", ',', new[] { "first", "second" })]
[InlineData("first,", ',', new[] { "first", "" })]
[InlineData(",second", ',', new[] { "", "second" })]
[InlineData(",first,second", ',', new[] { "", "first", "second" })]
[InlineData("first,second,", ',', new[] { "first", "second", "" })]
[InlineData("first,second,third", ',', new[] { "first", "second", "third" })]
[InlineData("first,,third", ',', new[] { "first", "", "third" })]
[InlineData(",first,second,third", ',', new[] { "", "first", "second", "third" })]
[InlineData("first,second,third,", ',', new[] { "first", "second", "third", "" })]
[InlineData(",first,second,third,", ',', new[] { "", "first", "second", "third", "" })]
[InlineData("Foo Bar Baz", ' ', new[] { "Foo", "Bar", "Baz" })]
[InlineData("Foo Bar Baz ", ' ', new[] { "Foo", "Bar", "Baz", "" })]
[InlineData(" Foo Bar Baz ", ' ', new[] { "", "Foo", "Bar", "Baz", "" })]
[InlineData(" Foo Bar Baz ", ' ', new[] { "", "Foo", "", "Bar", "Baz", "" })]
[InlineData("Foo Baz Bar", default(char), new[] { "Foo Baz Bar" })]
[InlineData("Foo Baz \x0000 Bar", default(char), new[] { "Foo Baz ", " Bar" })]
[InlineData("Foo Baz \x0000 Bar\x0000", default(char), new[] { "Foo Baz ", " Bar", "" })]
public static void SpanSplitCharSeparator(string valueParam, char separator, string[] expectedParam)
{
char[][] expected = expectedParam.Select(x => x.ToCharArray()).ToArray();
AssertEqual(expected, valueParam, valueParam.AsSpan().Split(separator));
}

[Theory]
[InlineData("", new[] { "" })]
[InlineData(" ", new[] { "", "" })]
[InlineData(" ", new[] { "", "", "", "", "", "" })]
[InlineData(" ", new[] { "", "", "" })]
[InlineData("ab", new[] { "ab" })]
[InlineData("a b", new[] { "a", "b" })]
[InlineData("a ", new[] { "a", "" })]
[InlineData(" b", new[] { "", "b" })]
[InlineData("Foo Bar Baz", new[] { "Foo", "Bar", "Baz" })]
[InlineData("Foo Bar Baz ", new[] { "Foo", "Bar", "Baz", "" })]
[InlineData(" Foo Bar Baz ", new[] { "", "Foo", "Bar", "Baz", "" })]
[InlineData(" Foo Bar Baz ", new[] { "", "Foo", "", "Bar", "Baz", "" })]
public static void SpanSplitDefaultCharSeparator(string valueParam, string[] expectedParam)
{
char[][] expected = expectedParam.Select(x => x.ToCharArray()).ToArray();
AssertEqual(expected, valueParam, valueParam.AsSpan().Split());
}

[Theory]
[InlineData(" Foo Bar Baz,", ", ", new[] { " Foo Bar Baz," })]
[InlineData(" Foo Bar Baz, ", ", ", new[] { " Foo Bar Baz", "" })]
[InlineData(", Foo Bar Baz, ", ", ", new[] { "", "Foo Bar Baz", "" })]
[InlineData(", Foo, Bar, Baz, ", ", ", new[] { "", "Foo", "Bar", "Baz", "" })]
[InlineData(", , Foo Bar, Baz", ", ", new[] { "", "", "Foo Bar", "Baz" })]
[InlineData(", , Foo Bar, Baz, , ", ", ", new[] { "", "", "Foo Bar", "Baz", "", "" })]
[InlineData(", , , , , ", ", ", new[] { "", "", "", "", "", "" })]
[InlineData(" ", " ", new[] { "", "", "", "", "", "" })]
[InlineData(" Foo, Bar Baz ", " ", new[] { "", "Foo, Bar", "Baz", "" })]
public static void SpanSplitStringSeparator(string valueParam, string separator, string[] expectedParam)
{
char[][] expected = expectedParam.Select(x => x.ToCharArray()).ToArray();
AssertEqual(expected, valueParam, valueParam.AsSpan().Split(separator));
}

private static void AssertEqual<T>(T[][] items, ReadOnlySpan<T> orig, SpanSplitEnumerator<T> source) where T : IEquatable<T>
{
foreach (var item in items)
{
Assert.True(source.MoveNext());
var slice = orig[source.Current];
Assert.Equal(item, slice.ToArray());
}
Assert.False(source.MoveNext());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
<Compile Include="ReadOnlySpan\SequenceEqual.long.cs" />
<Compile Include="ReadOnlySpan\SequenceEqual.T.cs" />
<Compile Include="ReadOnlySpan\Slice.cs" />
<Compile Include="ReadOnlySpan\Split.char.cs" />
<Compile Include="ReadOnlySpan\StartsWith.byte.cs" />
<Compile Include="ReadOnlySpan\StartsWith.long.cs" />
<Compile Include="ReadOnlySpan\StartsWith.T.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\MemoryDebugView.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.Globalization.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.Split.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MemoryExtensions.Trim.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MethodAccessException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\MidpointRounding.cs" />
Expand Down Expand Up @@ -839,6 +840,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.Char.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SpanHelpers.T.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SpanSplitEnumerator.T.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SR.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\StackOverflowException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\String.Comparison.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

namespace System
{
public static partial class MemoryExtensions
{
/// <summary>
/// Returns a type that allows for enumeration of each element within a split span
/// using a single space as a separator character.
/// </summary>
/// <param name="span">The source span to be enumerated.</param>
/// <returns>Returns a <see cref="System.SpanSplitEnumerator{T}"/>.</returns>
public static SpanSplitEnumerator<char> Split(this ReadOnlySpan<char> span)
=> new SpanSplitEnumerator<char>(span, ' ');

/// <summary>
/// Returns a type that allows for enumeration of each element within a split span
/// using the provided separator character.
/// </summary>
/// <param name="span">The source span to be enumerated.</param>
/// <param name="separator">The separator character to be used to split the provided span.</param>
/// <returns>Returns a <see cref="System.SpanSplitEnumerator{T}"/>.</returns>
public static SpanSplitEnumerator<char> Split(this ReadOnlySpan<char> span, char separator)
=> new SpanSplitEnumerator<char>(span, separator);

/// <summary>
/// Returns a type that allows for enumeration of each element within a split span
/// using the provided separator string.
/// </summary>
/// <param name="span">The source span to be enumerated.</param>
/// <param name="separator">The separator string to be used to split the provided span.</param>
/// <returns>Returns a <see cref="System.SpanSplitEnumerator{T}"/>.</returns>
public static SpanSplitEnumerator<char> Split(this ReadOnlySpan<char> span, string separator)
=> new SpanSplitEnumerator<char>(span, separator ?? string.Empty);
}
}
Loading