From 2da605832d09f8bd046517b463652cc52ec6553e Mon Sep 17 00:00:00 2001 From: bbartels Date: Tue, 26 Nov 2019 16:14:55 +0000 Subject: [PATCH 01/21] Added initial implementation of SpanSplitEnumerator --- .../System.Private.CoreLib.Shared.projitems | 1 + .../src/System/MemoryExtensions.Split.cs | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index ac793c38fc2767..f5fe39677f71ac 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -367,6 +367,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs new file mode 100644 index 00000000000000..c98dba461caa45 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -0,0 +1,78 @@ +// 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 + { + public static SpanSplitEnumerator Split(this ReadOnlySpan span) + => new SpanSplitEnumerator(span, ' '); + + public static SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) + => new SpanSplitEnumerator(span, separator); + + public static SpanSplitSequenceEnumerator Split(this ReadOnlySpan span, string separator) + => new SpanSplitSequenceEnumerator(span, separator); + } + + public ref struct SpanSplitEnumerator where T : IEquatable + { + private readonly ReadOnlySpan _sequence; + private readonly T _separator; + private int _offset; + private int _index; + + public SpanSplitEnumerator GetEnumerator() => this; + + internal SpanSplitEnumerator(ReadOnlySpan span, T separator) + { + _sequence = span; + _separator = separator; + _index = 0; + _offset = 0; + } + + public Range Current => new Range(_offset, _offset + _index - 1); + + public bool MoveNext() + { + if (_sequence.Length - _offset < _index) { return false; } + var slice = _sequence.Slice(_offset += _index); + + var nextIdx = slice.IndexOf(_separator); + _index = (nextIdx != -1 ? nextIdx : slice.Length) + 1; + return true; + } + } + + public ref struct SpanSplitSequenceEnumerator where T : IEquatable + { + private readonly ReadOnlySpan _sequence; + private readonly ReadOnlySpan _separator; + private int _offset; + private int _index; + + public SpanSplitSequenceEnumerator GetEnumerator() => this; + + internal SpanSplitSequenceEnumerator(ReadOnlySpan span, ReadOnlySpan separator) + { + _sequence = span; + _separator = separator; + _index = 0; + _offset = 0; + } + + public Range Current => new Range(_offset, _offset + _index - 1); + + public bool MoveNext() + { + if (_sequence.Length - _offset < _index) { return false; } + var slice = _sequence.Slice(_offset += _index); + + var nextIdx = slice.IndexOf(_separator); + _index = (nextIdx != -1 ? nextIdx : slice.Length) + 1; + return true; + } + } +} From e795f69bfe53a4efd3371f524ecd4b447ad73a23 Mon Sep 17 00:00:00 2001 From: bbartels Date: Fri, 22 May 2020 12:27:28 +0100 Subject: [PATCH 02/21] Merged SpanSplitEnumerator and SpanSplitSequenceEnumerator --- .../src/System/MemoryExtensions.Split.cs | 58 +++++++------------ 1 file changed, 21 insertions(+), 37 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs index c98dba461caa45..1f531d3d2f2aa4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -12,66 +12,50 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan span) public static SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) => new SpanSplitEnumerator(span, separator); - public static SpanSplitSequenceEnumerator Split(this ReadOnlySpan span, string separator) - => new SpanSplitSequenceEnumerator(span, separator); + public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) + => new SpanSplitEnumerator(span, separator); } public ref struct SpanSplitEnumerator where T : IEquatable { private readonly ReadOnlySpan _sequence; + private readonly ReadOnlySpan _separators; private readonly T _separator; + private readonly bool _isSequence; + private readonly int _separatorLength; private int _offset; private int _index; public SpanSplitEnumerator GetEnumerator() => this; + public readonly Range Current => new Range(_offset, _offset + _index - _separatorLength); - internal SpanSplitEnumerator(ReadOnlySpan span, T separator) + internal SpanSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separators) { _sequence = span; - _separator = separator; - _index = 0; - _offset = 0; + _separators = separators; + _separator = default; + _isSequence = true; + (_index, _offset) = (0, 0); + _separatorLength = _isSequence ? _separators.Length : 1; } - public Range Current => new Range(_offset, _offset + _index - 1); - - public bool MoveNext() - { - if (_sequence.Length - _offset < _index) { return false; } - var slice = _sequence.Slice(_offset += _index); - - var nextIdx = slice.IndexOf(_separator); - _index = (nextIdx != -1 ? nextIdx : slice.Length) + 1; - return true; - } - } - - public ref struct SpanSplitSequenceEnumerator where T : IEquatable - { - private readonly ReadOnlySpan _sequence; - private readonly ReadOnlySpan _separator; - private int _offset; - private int _index; - - public SpanSplitSequenceEnumerator GetEnumerator() => this; - - internal SpanSplitSequenceEnumerator(ReadOnlySpan span, ReadOnlySpan separator) + internal SpanSplitEnumerator(ReadOnlySpan span, T separator) { _sequence = span; _separator = separator; - _index = 0; - _offset = 0; + _separators = default; + _isSequence = false; + (_index, _offset) = (0, 0); + _separatorLength = _isSequence ? _separators.Length : 1; } - public Range Current => new Range(_offset, _offset + _index - 1); - public bool MoveNext() { - if (_sequence.Length - _offset < _index) { return false; } - var slice = _sequence.Slice(_offset += _index); + if ((_offset += _index) > _sequence.Length) { return false; } + var slice = _sequence.Slice(_offset); - var nextIdx = slice.IndexOf(_separator); - _index = (nextIdx != -1 ? nextIdx : slice.Length) + 1; + var nextIdx = _isSequence ? slice.IndexOf(_separators) : slice.IndexOf(_separator); + _index = (nextIdx != -1 ? nextIdx : slice.Length) + _separatorLength; return true; } } From 0bc1a962f84eabbbaba787054ed960752777acfd Mon Sep 17 00:00:00 2001 From: bbartels Date: Fri, 22 May 2020 12:29:50 +0100 Subject: [PATCH 03/21] Reordered .projitems entry --- .../src/System.Private.CoreLib.Shared.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index f5fe39677f71ac..2c72f3d22bd373 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -367,8 +367,8 @@ - + From addae397e99454ca3c26df01008489e5dd26cd08 Mon Sep 17 00:00:00 2001 From: bbartels Date: Fri, 22 May 2020 12:35:28 +0100 Subject: [PATCH 04/21] Exposed System.Memory API additions --- src/libraries/System.Memory/ref/System.Memory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 8ce1a84b0cbc69..2f78aa8448d564 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -169,6 +169,9 @@ public static void Sort(this System.Span span, System.Comparison compar public static void Sort(this System.Span keys, System.Span items) { } public static void Sort(this System.Span keys, System.Span items, TComparer comparer) where TComparer : System.Collections.Generic.IComparer? { } public static void Sort(this System.Span keys, System.Span items, System.Comparison comparison) { } + public static SpanSplitEnumerator Split(this ReadOnlySpan span) { } + public static SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) { } + public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) { } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) #nullable disable From d153ebd8b7cffc12f6dbaf40e4224b7e55d0776a Mon Sep 17 00:00:00 2001 From: bbartels Date: Mon, 25 May 2020 06:15:21 +0100 Subject: [PATCH 05/21] Added SpanSplitEnumerator Tests --- .../System.Memory/tests/ReadOnlySpan/Split.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs new file mode 100644 index 00000000000000..130a3a4681a95c --- /dev/null +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs @@ -0,0 +1,83 @@ +// 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 Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void SplitNoMatchSingleResult() + { + ReadOnlySpan value = "a b"; + + string expected = value.ToString(); + var enumerator = value.Split(','); + Assert.True(enumerator.MoveNext()); + Assert.Equal(expected, value[enumerator.Current].ToString()); + } + + [Theory] + [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", "" })] + public static void SpanSplitCharSeparator(string valueParam, char separator, string[] expectedParam) + { + char[][] expected = expectedParam.Select(x => x.ToCharArray()).ToArray(); + AssertEqual(valueParam, valueParam.AsSpan().Split(separator), expected); + } + + [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(" 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(valueParam, valueParam.AsSpan().Split(separator), expected); + } + + private static void AssertEqual(ReadOnlySpan orig, SpanSplitEnumerator source, T[][] items) where T : IEquatable + { + foreach (var item in items) + { + Assert.True(source.MoveNext()); + var slice = orig[source.Current]; + Assert.Equal(item, slice.ToArray()); + } + Assert.False(source.MoveNext()); + } + } +} From 3a5b758c524346849695512feefa313c00f853a9 Mon Sep 17 00:00:00 2001 From: Benjamin Bartels Date: Thu, 28 May 2020 08:45:50 +0100 Subject: [PATCH 06/21] Apply suggestions from code review Co-authored-by: Layomi Akinrinade --- .../src/System/MemoryExtensions.Split.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs index 1f531d3d2f2aa4..4ec58f2bc808c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -18,8 +18,8 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan span, stri public ref struct SpanSplitEnumerator where T : IEquatable { - private readonly ReadOnlySpan _sequence; - private readonly ReadOnlySpan _separators; + private readonly ReadOnlySpan _buffer; + private readonly ReadOnlySpan _separatorSequence; private readonly T _separator; private readonly bool _isSequence; private readonly int _separatorLength; @@ -29,17 +29,17 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan span, stri public SpanSplitEnumerator GetEnumerator() => this; public readonly Range Current => new Range(_offset, _offset + _index - _separatorLength); - internal SpanSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separators) + internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) { _sequence = span; _separators = separators; - _separator = default; + _separator = default!; _isSequence = true; (_index, _offset) = (0, 0); _separatorLength = _isSequence ? _separators.Length : 1; } - internal SpanSplitEnumerator(ReadOnlySpan span, T separator) + internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) { _sequence = span; _separator = separator; From ebed6697a7cdf65c4ca023193c1183dd2672baa4 Mon Sep 17 00:00:00 2001 From: Benjamin Bartels Date: Thu, 28 May 2020 08:46:46 +0100 Subject: [PATCH 07/21] Apply suggestions from code review Co-authored-by: Layomi Akinrinade --- .../src/System/MemoryExtensions.Split.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs index 4ec58f2bc808c9..29a8475c4263d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -36,7 +36,7 @@ internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) _separator = default!; _isSequence = true; (_index, _offset) = (0, 0); - _separatorLength = _isSequence ? _separators.Length : 1; + _separatorLength = _separators.Length; } internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) @@ -46,7 +46,7 @@ internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) _separators = default; _isSequence = false; (_index, _offset) = (0, 0); - _separatorLength = _isSequence ? _separators.Length : 1; + _separatorLength = 1; } public bool MoveNext() From 5c5b46388f40503ad8077fe70f0624052abcc188 Mon Sep 17 00:00:00 2001 From: bbartels Date: Thu, 28 May 2020 08:57:10 +0100 Subject: [PATCH 08/21] Moved SpanSplitEnumerator to separate file --- .../System.Private.CoreLib.Shared.projitems | 1 + .../src/System/MemoryExtensions.Split.cs | 44 ---------------- .../src/System/SpanSplitEnumerator.T.cs | 51 +++++++++++++++++++ 3 files changed, 52 insertions(+), 44 deletions(-) create mode 100644 src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 2c72f3d22bd373..9f6dbf4f1c9826 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -785,6 +785,7 @@ + diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs index 29a8475c4263d8..af1a7df0cf1827 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -15,48 +15,4 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan span, char public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) => new SpanSplitEnumerator(span, separator); } - - public ref struct SpanSplitEnumerator where T : IEquatable - { - private readonly ReadOnlySpan _buffer; - private readonly ReadOnlySpan _separatorSequence; - private readonly T _separator; - private readonly bool _isSequence; - private readonly int _separatorLength; - private int _offset; - private int _index; - - public SpanSplitEnumerator GetEnumerator() => this; - public readonly Range Current => new Range(_offset, _offset + _index - _separatorLength); - - internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) - { - _sequence = span; - _separators = separators; - _separator = default!; - _isSequence = true; - (_index, _offset) = (0, 0); - _separatorLength = _separators.Length; - } - - internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) - { - _sequence = span; - _separator = separator; - _separators = default; - _isSequence = false; - (_index, _offset) = (0, 0); - _separatorLength = 1; - } - - public bool MoveNext() - { - if ((_offset += _index) > _sequence.Length) { return false; } - var slice = _sequence.Slice(_offset); - - var nextIdx = _isSequence ? slice.IndexOf(_separators) : slice.IndexOf(_separator); - _index = (nextIdx != -1 ? nextIdx : slice.Length) + _separatorLength; - return true; - } - } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs new file mode 100644 index 00000000000000..37d51f7506c9eb --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs @@ -0,0 +1,51 @@ +// 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 ref struct SpanSplitEnumerator where T : IEquatable + { + private readonly ReadOnlySpan _buffer; + private readonly ReadOnlySpan _separatorSequence; + private readonly T _separator; + private readonly bool _isSequence; + private readonly int _separatorLength; + private int _offset; + private int _index; + + public SpanSplitEnumerator GetEnumerator() => this; + public readonly Range Current => new Range(_offset, _offset + _index - _separatorLength); + + internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) + { + _sequence = span; + _separators = separators; + _separator = default!; + _isSequence = true; + (_index, _offset) = (0, 0); + _separatorLength = _separators.Length; + } + + internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) + { + _sequence = span; + _separator = separator; + _separators = default; + _isSequence = false; + (_index, _offset) = (0, 0); + _separatorLength = 1; + } + + public bool MoveNext() + { + _offset += _index; + if (_offset > _sequence.Length) { return false; } + var slice = _sequence.Slice(_offset); + + var nextIdx = _isSequence ? slice.IndexOf(_separators) : slice.IndexOf(_separator); + _index = (nextIdx != -1 ? nextIdx : slice.Length) + _separatorLength; + return true; + } + } +} From 97a2a448f0e3e77a7fe115b1ddd4ed681f435c92 Mon Sep 17 00:00:00 2001 From: bbartels Date: Thu, 28 May 2020 09:04:43 +0100 Subject: [PATCH 09/21] Applied feedback to ReadOnlySpan.Split tests --- src/libraries/System.Memory/ref/System.Memory.cs | 12 +++++++++--- .../System.Memory/tests/System.Memory.Tests.csproj | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 2f78aa8448d564..4c19abbe1a485e 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -169,9 +169,9 @@ public static void Sort(this System.Span span, System.Comparison compar public static void Sort(this System.Span keys, System.Span items) { } public static void Sort(this System.Span keys, System.Span items, TComparer comparer) where TComparer : System.Collections.Generic.IComparer? { } public static void Sort(this System.Span keys, System.Span items, System.Comparison comparison) { } - public static SpanSplitEnumerator Split(this ReadOnlySpan span) { } - public static SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) { } - public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) { } + public static System.SpanSplitEnumerator Split(this ReadOnlySpan span) { throw null; } + public static System.SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) { throw null; } + public static System.SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) { throw null; } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) #nullable disable @@ -315,6 +315,12 @@ public static System.Span Trim(this System.Span span, T trimElement) [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public object? GetObject() { throw null; } } + public ref struct SpanSplitEnumerator where T : System.IEquatable + { + public System.MemoryExtensions.SpanSplitEnumerator GetEnumerator() { throw null; } + public readonly System.Range Current { get { throw null; } } + public bool MoveNext() { throw null; } + } } namespace System.Buffers { diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index 368de3a6676636..177dcee652abce 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -163,6 +163,7 @@ + From 549c41bdaec0f23b75c78519fb39edeb70cdb335 Mon Sep 17 00:00:00 2001 From: bbartels Date: Thu, 28 May 2020 09:20:10 +0100 Subject: [PATCH 10/21] Renamed parameters/fields --- .../src/System/SpanSplitEnumerator.T.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs index 37d51f7506c9eb..6a364bb55f157f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs @@ -19,19 +19,19 @@ namespace System internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) { - _sequence = span; - _separators = separators; + _buffer = buffer; + _separatorSequence = separator; _separator = default!; _isSequence = true; (_index, _offset) = (0, 0); - _separatorLength = _separators.Length; + _separatorLength = _separatorSequence.Length; } internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) { - _sequence = span; + _buffer = buffer; _separator = separator; - _separators = default; + _separatorSequence = default; _isSequence = false; (_index, _offset) = (0, 0); _separatorLength = 1; @@ -40,10 +40,10 @@ internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) public bool MoveNext() { _offset += _index; - if (_offset > _sequence.Length) { return false; } - var slice = _sequence.Slice(_offset); + if (_offset > _buffer.Length) { return false; } + var slice = _buffer.Slice(_offset); - var nextIdx = _isSequence ? slice.IndexOf(_separators) : slice.IndexOf(_separator); + var nextIdx = _isSequence ? slice.IndexOf(_separatorSequence) : slice.IndexOf(_separator); _index = (nextIdx != -1 ? nextIdx : slice.Length) + _separatorLength; return true; } From 4c8c6ac60f7c510bb42d5f13003fa2357633fd0e Mon Sep 17 00:00:00 2001 From: bbartels Date: Thu, 28 May 2020 09:34:18 +0100 Subject: [PATCH 11/21] Removed mistaken compile include --- .../src/System.Private.CoreLib.Shared.projitems | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 1069b1738fd0e3..88df2847ee5f36 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -838,7 +838,6 @@ - From ea512e4ef591e4640d1d26782eb35c14c79e01c4 Mon Sep 17 00:00:00 2001 From: bbartels Date: Sat, 30 May 2020 06:44:55 +0100 Subject: [PATCH 12/21] Fixed incorrect namespace on GetEnumeartor() return type --- src/libraries/System.Memory/ref/System.Memory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index ee28d59369173b..7ff95a4436880a 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -161,7 +161,7 @@ public static void Sort(this System.Span keys, System.Span where T : System.IEquatable { - public System.MemoryExtensions.SpanSplitEnumerator GetEnumerator() { throw null; } + public System.SpanSplitEnumerator GetEnumerator() { throw null; } public readonly System.Range Current { get { throw null; } } public bool MoveNext() { throw null; } } From af9ae45eb575989ddb13cebceea41926ca81612c Mon Sep 17 00:00:00 2001 From: bbartels Date: Sat, 30 May 2020 13:02:38 +0100 Subject: [PATCH 13/21] Applied feedback for ReadOnlySpan.Split tests --- .../System.Memory/tests/ReadOnlySpan/Split.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs index 130a3a4681a95c..13d9bfa49f74e0 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs @@ -2,6 +2,8 @@ // 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; +using System.Linq; using Xunit; namespace System.SpanTests @@ -19,6 +21,14 @@ public static void SplitNoMatchSingleResult() Assert.Equal(expected, value[enumerator.Current].ToString()); } + [Fact] + public static void DefaultSpanSplitEnumeratorBehavior() + { + Assert.Equal(new SpanSplitEnumerator().Current, new Range(0, 0)); + Assert.True(new SpanSplitEnumerator().MoveNext()); + new SpanSplitEnumerator().GetEnumerator(); + } + [Theory] [InlineData("", ',', new[] { "" })] [InlineData(",", ',', new[] { "", "" })] @@ -48,6 +58,9 @@ public static void SplitNoMatchSingleResult() [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(); From 9534d0817c4e09314b6415ba3c08bbb2eb2137a1 Mon Sep 17 00:00:00 2001 From: bbartels Date: Sun, 31 May 2020 10:11:55 +0100 Subject: [PATCH 14/21] Added XML Documentation to public members --- .../src/System/MemoryExtensions.Split.cs | 20 +++++++++++++++++++ .../src/System/SpanSplitEnumerator.T.cs | 17 ++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs index af1a7df0cf1827..5bb0820d7d0dc4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -6,12 +6,32 @@ namespace System { public static partial class MemoryExtensions { + /// + /// Returns a type that allows for enumeration of each element within a split span + /// using a single space as a separator character. + /// + /// The source span to be enumerated. + /// Returns a . public static SpanSplitEnumerator Split(this ReadOnlySpan span) => new SpanSplitEnumerator(span, ' '); + /// + /// Returns a type that allows for enumeration of each element within a split span + /// using the provided separator character. + /// + /// The source span to be enumerated. + /// The separator character to be used to split the provided span. + /// Returns a . public static SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) => new SpanSplitEnumerator(span, separator); + /// + /// Returns a type that allows for enumeration of each element within a split span + /// using the provided separator string. + /// + /// The source span to be enumerated. + /// The separator string to be used to split the provided span. + /// Returns a . public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) => new SpanSplitEnumerator(span, separator); } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs index 6a364bb55f157f..0124bedc5d3ab6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs @@ -4,6 +4,10 @@ namespace System { + /// + /// allows for enumeration of each element within a + /// that has been split using a provided separator. + /// public ref struct SpanSplitEnumerator where T : IEquatable { private readonly ReadOnlySpan _buffer; @@ -14,7 +18,16 @@ namespace System private int _offset; private int _index; + /// + /// Returns an enumerator that allows for iteration over the split span. + /// + /// Returns a that can be used to iterate over the split span. public SpanSplitEnumerator GetEnumerator() => this; + + /// + /// Returns the current element of the enumeration. + /// + /// Returns a struct that defines the bounds of the current element withing the source span. public readonly Range Current => new Range(_offset, _offset + _index - _separatorLength); internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) @@ -37,6 +50,10 @@ internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) _separatorLength = 1; } + /// + /// Advances the enumerator to the next element of the split span. + /// + /// Returns a bool indicating whether an element is available. public bool MoveNext() { _offset += _index; From be144a64d59e17400ec59dc57864d9c79ed8c364 Mon Sep 17 00:00:00 2001 From: bbartels Date: Sun, 31 May 2020 10:28:07 +0100 Subject: [PATCH 15/21] Fixed cref reference to SpanSplitEnumerator in XML Documentation --- .../src/System/MemoryExtensions.Split.cs | 6 +++--- .../src/System/SpanSplitEnumerator.T.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs index 5bb0820d7d0dc4..7f2a412b237eb3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -11,7 +11,7 @@ public static partial class MemoryExtensions /// using a single space as a separator character. /// /// The source span to be enumerated. - /// Returns a . + /// Returns a . public static SpanSplitEnumerator Split(this ReadOnlySpan span) => new SpanSplitEnumerator(span, ' '); @@ -21,7 +21,7 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan span) /// /// The source span to be enumerated. /// The separator character to be used to split the provided span. - /// Returns a . + /// Returns a . public static SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) => new SpanSplitEnumerator(span, separator); @@ -31,7 +31,7 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan span, char /// /// The source span to be enumerated. /// The separator string to be used to split the provided span. - /// Returns a . + /// Returns a . public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) => new SpanSplitEnumerator(span, separator); } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs index 0124bedc5d3ab6..ead33cbb891f98 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs @@ -5,7 +5,7 @@ namespace System { /// - /// allows for enumeration of each element within a + /// allows for enumeration of each element within a /// that has been split using a provided separator. /// public ref struct SpanSplitEnumerator where T : IEquatable @@ -21,7 +21,7 @@ namespace System /// /// Returns an enumerator that allows for iteration over the split span. /// - /// Returns a that can be used to iterate over the split span. + /// Returns a that can be used to iterate over the split span. public SpanSplitEnumerator GetEnumerator() => this; /// From a906b398c85458498929b13e76c80730f745a578 Mon Sep 17 00:00:00 2001 From: bbartels Date: Tue, 2 Jun 2020 17:39:37 +0100 Subject: [PATCH 16/21] Fixed System.Memory.cs entries missing fully qualified identifier --- src/libraries/System.Memory/ref/System.Memory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 7ff95a4436880a..18b6af79a9e9ed 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -91,9 +91,9 @@ public static void Sort(this System.Span span, System.Comparison compar public static void Sort(this System.Span keys, System.Span items) { } public static void Sort(this System.Span keys, System.Span items, TComparer comparer) where TComparer : System.Collections.Generic.IComparer? { } public static void Sort(this System.Span keys, System.Span items, System.Comparison comparison) { } - public static System.SpanSplitEnumerator Split(this ReadOnlySpan span) { throw null; } - public static System.SpanSplitEnumerator Split(this ReadOnlySpan span, char separator) { throw null; } - public static System.SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) { throw null; } + public static System.SpanSplitEnumerator Split(this System.ReadOnlySpan span) { throw null; } + public static System.SpanSplitEnumerator Split(this System.ReadOnlySpan span, char separator) { throw null; } + public static System.SpanSplitEnumerator Split(this System.ReadOnlySpan span, string separator) { throw null; } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } public static bool StartsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } public static bool StartsWith(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable { throw null; } From d42365c4215fde8f8fb1674491fb4bb61cebb5de Mon Sep 17 00:00:00 2001 From: Benjamin Bartels Date: Wed, 3 Jun 2020 03:49:57 +0100 Subject: [PATCH 17/21] Apply suggestions from code review Co-authored-by: Layomi Akinrinade --- src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs | 1 - .../src/System/MemoryExtensions.Split.cs | 2 +- .../src/System/SpanSplitEnumerator.T.cs | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs index 13d9bfa49f74e0..971671168f7ad0 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs @@ -2,7 +2,6 @@ // 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; using System.Linq; using Xunit; diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs index 7f2a412b237eb3..77426e5843e6c9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.Split.cs @@ -33,6 +33,6 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan span, char /// The separator string to be used to split the provided span. /// Returns a . public static SpanSplitEnumerator Split(this ReadOnlySpan span, string separator) - => new SpanSplitEnumerator(span, separator); + => new SpanSplitEnumerator(span, separator ?? string.Empty); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs index ead33cbb891f98..63900018c725f5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs @@ -27,7 +27,7 @@ namespace System /// /// Returns the current element of the enumeration. /// - /// Returns a struct that defines the bounds of the current element withing the source span. + /// Returns a instance that indicates the bounds of the current element withing the source span. public readonly Range Current => new Range(_offset, _offset + _index - _separatorLength); internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) @@ -51,9 +51,9 @@ internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) } /// - /// Advances the enumerator to the next element of the split span. + /// Advances the enumerator to the next element of the enumeration. /// - /// Returns a bool indicating whether an element is available. + /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the enumeration. public bool MoveNext() { _offset += _index; From 20486ab1d92be69b3fe1b69676a673a565c8edfc Mon Sep 17 00:00:00 2001 From: bbartels Date: Wed, 3 Jun 2020 03:53:18 +0100 Subject: [PATCH 18/21] Moved SpanSplit Tests to correct file --- .../tests/ReadOnlySpan/{Split.cs => Split.char.cs} | 0 src/libraries/System.Memory/tests/System.Memory.Tests.csproj | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/libraries/System.Memory/tests/ReadOnlySpan/{Split.cs => Split.char.cs} (100%) diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs similarity index 100% rename from src/libraries/System.Memory/tests/ReadOnlySpan/Split.cs rename to src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index 30ec78a7f377aa..952b6d8f9dff5d 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -165,7 +165,7 @@ - + From 443dad650921a0b9f532461e7419268db7e15832 Mon Sep 17 00:00:00 2001 From: bbartels Date: Thu, 4 Jun 2020 09:57:20 +0100 Subject: [PATCH 19/21] Applied review feedback --- .../tests/ReadOnlySpan/Split.char.cs | 175 +++++++++++++++++- .../src/System/SpanSplitEnumerator.T.cs | 65 ++++--- 2 files changed, 211 insertions(+), 29 deletions(-) diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs index 971671168f7ad0..b2297d9027d6bf 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs @@ -23,14 +23,157 @@ public static void SplitNoMatchSingleResult() [Fact] public static void DefaultSpanSplitEnumeratorBehavior() { - Assert.Equal(new SpanSplitEnumerator().Current, new Range(0, 0)); - Assert.True(new SpanSplitEnumerator().MoveNext()); - new SpanSplitEnumerator().GetEnumerator(); + var charSpanEnumerator = new SpanSplitEnumerator(); + Assert.Equal(new Range(0, 0), charSpanEnumerator.Current); + Assert.False(charSpanEnumerator.MoveNext()); + // Implicit DoesNotThrow assertion + charSpanEnumerator.GetEnumerator(); + var stringSpanEnumerator = new SpanSplitEnumerator(); + Assert.Equal(new Range(0, 0), stringSpanEnumerator.Current); + Assert.False(stringSpanEnumerator.MoveNext()); + stringSpanEnumerator.GetEnumerator(); + } + + [Fact] + public static void ValidateArguments_OverloadWithoutSeparator() + { + ReadOnlySpan buffer = default; + + SpanSplitEnumerator 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 buffer = default; + + SpanSplitEnumerator 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 buffer = default; + + SpanSplitEnumerator 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" })] @@ -63,7 +206,26 @@ public static void DefaultSpanSplitEnumeratorBehavior() public static void SpanSplitCharSeparator(string valueParam, char separator, string[] expectedParam) { char[][] expected = expectedParam.Select(x => x.ToCharArray()).ToArray(); - AssertEqual(valueParam, valueParam.AsSpan().Split(separator), expected); + 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, char separator, string[] expectedParam) + { + char[][] expected = expectedParam.Select(x => x.ToCharArray()).ToArray(); + AssertEqual(expected, valueParam, valueParam.AsSpan().Split()); } [Theory] @@ -74,14 +236,15 @@ public static void SpanSplitCharSeparator(string valueParam, char separator, str [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(valueParam, valueParam.AsSpan().Split(separator), expected); + AssertEqual(expected, valueParam, valueParam.AsSpan().Split(separator)); } - private static void AssertEqual(ReadOnlySpan orig, SpanSplitEnumerator source, T[][] items) where T : IEquatable + private static void AssertEqual(T[][] items, ReadOnlySpan orig, SpanSplitEnumerator source,) where T : IEquatable { foreach (var item in items) { diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs index 63900018c725f5..d12c940158f6f7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. namespace System -{ +{ /// /// allows for enumeration of each element within a /// that has been split using a provided separator. @@ -11,43 +11,55 @@ namespace System public ref struct SpanSplitEnumerator where T : IEquatable { private readonly ReadOnlySpan _buffer; - private readonly ReadOnlySpan _separatorSequence; + + private readonly ReadOnlySpan _separators; private readonly T _separator; - private readonly bool _isSequence; + private readonly int _separatorLength; - private int _offset; - private int _index; + private readonly bool _splitOnSingleToken; + + private readonly bool _isInitialized; + + private int _startCurrent; + private int _endCurrent; + private int _startNext; /// /// Returns an enumerator that allows for iteration over the split span. /// /// Returns a that can be used to iterate over the split span. public SpanSplitEnumerator GetEnumerator() => this; - + /// /// Returns the current element of the enumeration. /// /// Returns a instance that indicates the bounds of the current element withing the source span. - public readonly Range Current => new Range(_offset, _offset + _index - _separatorLength); + public Range Current => new Range(_startCurrent, _endCurrent); - internal SpanSplitEnumerator(ReadOnlySpan buffer, ReadOnlySpan separator) + internal SpanSplitEnumerator(ReadOnlySpan span, ReadOnlySpan separators) { - _buffer = buffer; - _separatorSequence = separator; + _isInitialized = true; + _buffer = span; + _separators = separators; _separator = default!; - _isSequence = true; - (_index, _offset) = (0, 0); - _separatorLength = _separatorSequence.Length; + _splitOnSingleToken = false; + _separatorLength = _separators.Length != 0 ? _separators.Length : 1; + _startCurrent = 0; + _endCurrent = 0; + _startNext = 0; } - internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) + internal SpanSplitEnumerator(ReadOnlySpan span, T separator) { - _buffer = buffer; + _isInitialized = true; + _buffer = span; _separator = separator; - _separatorSequence = default; - _isSequence = false; - (_index, _offset) = (0, 0); + _separators = default; + _splitOnSingleToken = true; _separatorLength = 1; + _startCurrent = 0; + _endCurrent = 0; + _startNext = 0; } /// @@ -56,12 +68,19 @@ internal SpanSplitEnumerator(ReadOnlySpan buffer, T separator) /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the enumeration. public bool MoveNext() { - _offset += _index; - if (_offset > _buffer.Length) { return false; } - var slice = _buffer.Slice(_offset); + if (!_isInitialized || _startNext > _buffer.Length) + { + return false; + } + + ReadOnlySpan slice = _buffer.Slice(_startNext); + _startCurrent = _startNext; + + int separatorIndex = _splitOnSingleToken ? slice.IndexOf(_separator) : slice.IndexOf(_separators); + int elementLength = (separatorIndex != -1 ? separatorIndex : slice.Length); - var nextIdx = _isSequence ? slice.IndexOf(_separatorSequence) : slice.IndexOf(_separator); - _index = (nextIdx != -1 ? nextIdx : slice.Length) + _separatorLength; + _endCurrent = _startCurrent + elementLength; + _startNext = _endCurrent + _separatorLength; return true; } } From dab73351e18e341a716b9a342448a597f67a3f2f Mon Sep 17 00:00:00 2001 From: bbartels Date: Thu, 4 Jun 2020 18:02:25 +0100 Subject: [PATCH 20/21] Removed trailing whitespace --- .../System.Memory/tests/ReadOnlySpan/Split.char.cs | 2 ++ .../src/System/SpanSplitEnumerator.T.cs | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs index b2297d9027d6bf..a1e99ca0231c39 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs @@ -26,8 +26,10 @@ public static void DefaultSpanSplitEnumeratorBehavior() var charSpanEnumerator = new SpanSplitEnumerator(); Assert.Equal(new Range(0, 0), charSpanEnumerator.Current); Assert.False(charSpanEnumerator.MoveNext()); + // Implicit DoesNotThrow assertion charSpanEnumerator.GetEnumerator(); + var stringSpanEnumerator = new SpanSplitEnumerator(); Assert.Equal(new Range(0, 0), stringSpanEnumerator.Current); Assert.False(stringSpanEnumerator.MoveNext()); diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs index d12c940158f6f7..69382459b26e4a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanSplitEnumerator.T.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. namespace System -{ +{ /// /// allows for enumeration of each element within a /// that has been split using a provided separator. @@ -14,10 +14,10 @@ namespace System private readonly ReadOnlySpan _separators; private readonly T _separator; - + private readonly int _separatorLength; private readonly bool _splitOnSingleToken; - + private readonly bool _isInitialized; private int _startCurrent; @@ -29,7 +29,7 @@ namespace System /// /// Returns a that can be used to iterate over the split span. public SpanSplitEnumerator GetEnumerator() => this; - + /// /// Returns the current element of the enumeration. /// From be90021519476bb56c8eb53e39b50c2f293687df Mon Sep 17 00:00:00 2001 From: bbartels Date: Thu, 4 Jun 2020 19:14:17 +0100 Subject: [PATCH 21/21] Fixed Unit Tests --- src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs index a1e99ca0231c39..10a832270163f4 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.char.cs @@ -224,7 +224,7 @@ public static void SpanSplitCharSeparator(string valueParam, char separator, str [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, char separator, string[] expectedParam) + public static void SpanSplitDefaultCharSeparator(string valueParam, string[] expectedParam) { char[][] expected = expectedParam.Select(x => x.ToCharArray()).ToArray(); AssertEqual(expected, valueParam, valueParam.AsSpan().Split()); @@ -246,7 +246,7 @@ public static void SpanSplitStringSeparator(string valueParam, string separator, AssertEqual(expected, valueParam, valueParam.AsSpan().Split(separator)); } - private static void AssertEqual(T[][] items, ReadOnlySpan orig, SpanSplitEnumerator source,) where T : IEquatable + private static void AssertEqual(T[][] items, ReadOnlySpan orig, SpanSplitEnumerator source) where T : IEquatable { foreach (var item in items) {