Skip to content

Add whitespace handling methods to MemoryExtensions #111439

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ public static partial class MemoryExtensions
public static bool ContainsAnyInRange<T>(this System.ReadOnlySpan<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
public static bool ContainsAnyInRange<T>(this System.Span<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
public static bool ContainsAnyWhiteSpace(this System.ReadOnlySpan<char> span) { throw null; }
public static void CopyTo<T>(this T[]? source, System.Memory<T> destination) { }
public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
Expand Down Expand Up @@ -331,6 +332,7 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
public static int IndexOfAnyExceptInRange<T>(this System.ReadOnlySpan<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
public static int IndexOfAnyExceptInRange<T>(this System.Span<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
public static int IndexOfAnyExceptWhiteSpace(this System.ReadOnlySpan<char> span) { throw null; }
public static int IndexOf<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T>? { throw null; }
public static int IndexOf<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T>? { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
Expand All @@ -340,6 +342,7 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
public static int IndexOfAnyInRange<T>(this System.ReadOnlySpan<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
public static int IndexOfAnyInRange<T>(this System.Span<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
public static int IndexOfAnyWhiteSpace(this System.ReadOnlySpan<char> span) { throw null; }
public static bool IsWhiteSpace(this System.ReadOnlySpan<char> span) { throw null; }
public static int LastIndexOf(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> value, System.StringComparison comparisonType) { throw null; }
public static int LastIndexOfAny<T>(this System.ReadOnlySpan<T> span, System.Buffers.SearchValues<T> values) where T : System.IEquatable<T>? { throw null; }
Expand Down Expand Up @@ -372,6 +375,7 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
public static int LastIndexOfAnyExceptInRange<T>(this System.ReadOnlySpan<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
public static int LastIndexOfAnyExceptInRange<T>(this System.Span<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
public static int LastIndexOfAnyExceptWhiteSpace(this System.ReadOnlySpan<char> span) { throw null; }
public static int LastIndexOf<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T>? { throw null; }
public static int LastIndexOf<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T>? { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
Expand All @@ -381,6 +385,7 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
public static int LastIndexOfAnyInRange<T>(this System.ReadOnlySpan<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
public static int LastIndexOfAnyInRange<T>(this System.Span<T> span, T lowInclusive, T highInclusive) where T : System.IComparable<T> { throw null; }
public static int LastIndexOfAnyWhiteSpace(this System.ReadOnlySpan<char> span) { throw null; }
public static bool Overlaps<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other) { throw null; }
public static bool Overlaps<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> other, out int elementOffset) { throw null; }
[System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute(-1)]
Expand Down
261 changes: 261 additions & 0 deletions src/libraries/System.Memory/tests/ReadOnlySpan/WhiteSpace.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System.Collections.Generic;
using System.Runtime.InteropServices;
using Xunit;

namespace System.SpanTests
{
public static partial class ReadOnlySpanTests
{
[Fact]
public static void IsWhiteSpace_True()
{
Assert.True(Span<char>.Empty.IsWhiteSpace());

List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

Assert.True(CollectionsMarshal.AsSpan(chars).IsWhiteSpace());
}

[Fact]
public static void IsWhiteSpace_False()
{
List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

var index = chars.Count;
chars.AddRange(chars.ToArray());
chars.Insert(index, ' ');
var span = CollectionsMarshal.AsSpan(chars);

for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
{
chars[index] = (char)i;
Assert.False(span.IsWhiteSpace());
}
}
}

[Fact]
public static void ContainsAnyWhiteSpace_Found()
{
List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

var index = chars.Count;
chars.AddRange(chars.ToArray());
chars.Insert(index, ' ');
var span = CollectionsMarshal.AsSpan(chars);

for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
{
chars[index] = (char)i;
Assert.True(span.ContainsAnyWhiteSpace());
}
}
}

[Fact]
public static void ContainsAnyWhiteSpace_NotFound()
{
Assert.False(Span<char>.Empty.ContainsAnyWhiteSpace());

List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

Assert.False(CollectionsMarshal.AsSpan(chars).ContainsAnyWhiteSpace());
}

[Fact]
public static void IndexOfAnyWhiteSpace_Found()
{
List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

var index = chars.Count;
chars.AddRange(chars.ToArray());
chars.Insert(index, ' ');
chars.Insert(index, ' ');
var span = CollectionsMarshal.AsSpan(chars);

for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
{
chars[index] = (char)i;
chars[index + 1] = (char)i;
Assert.Equal(index, span.IndexOfAnyWhiteSpace());
}
}
}

[Fact]
public static void IndexOfAnyWhiteSpace_NotFound()
{
Assert.Equal(-1, Span<char>.Empty.IndexOfAnyWhiteSpace());

List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

Assert.Equal(-1, CollectionsMarshal.AsSpan(chars).IndexOfAnyWhiteSpace());
}

[Fact]
public static void IndexOfAnyExceptWhiteSpace_Found()
{
List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

var index = chars.Count;
chars.AddRange(chars.ToArray());
chars.Insert(index, ' ');
chars.Insert(index, ' ');
var span = CollectionsMarshal.AsSpan(chars);

for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
{
chars[index] = (char)i;
chars[index + 1] = (char)i;
Assert.Equal(index, span.IndexOfAnyExceptWhiteSpace());
}
}
}

[Fact]
public static void IndexOfAnyExceptWhiteSpace_NotFound()
{
Assert.Equal(-1, Span<char>.Empty.IndexOfAnyExceptWhiteSpace());

List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

Assert.Equal(-1, CollectionsMarshal.AsSpan(chars).IndexOfAnyExceptWhiteSpace());
}

[Fact]
public static void LastIndexOfAnyWhiteSpace_Found()
{
List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

var index = chars.Count;
chars.AddRange(chars.ToArray());
chars.Insert(index, ' ');
chars.Insert(index, ' ');
var span = CollectionsMarshal.AsSpan(chars);

for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
{
chars[index] = (char)i;
chars[index + 1] = (char)i;
Assert.Equal(index + 1, span.LastIndexOfAnyWhiteSpace());
}
}
}

[Fact]
public static void LastIndexOfAnyWhiteSpace_NotFound()
{
Assert.Equal(-1, Span<char>.Empty.LastIndexOfAnyWhiteSpace());

List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

Assert.Equal(-1, CollectionsMarshal.AsSpan(chars).LastIndexOfAnyWhiteSpace());
}

[Fact]
public static void LastIndexOfAnyExceptWhiteSpace_Found()
{
List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

var index = chars.Count;
chars.AddRange(chars.ToArray());
chars.Insert(index, ' ');
chars.Insert(index, ' ');
var span = CollectionsMarshal.AsSpan(chars);

for (int i = 0; i <= char.MaxValue; i++)
{
if (!char.IsWhiteSpace((char)i))
{
chars[index] = (char)i;
chars[index + 1] = (char)i;
Assert.Equal(index + 1, span.LastIndexOfAnyExceptWhiteSpace());
}
}
}

[Fact]
public static void LastIndexOfAnyExceptWhiteSpace_NotFound()
{
Assert.Equal(-1, Span<char>.Empty.LastIndexOfAnyExceptWhiteSpace());

List<char> chars = [];
for (int i = 0; i <= char.MaxValue; i++)
{
if (char.IsWhiteSpace((char)i))
chars.Add((char)i);
}

Assert.Equal(-1, CollectionsMarshal.AsSpan(chars).LastIndexOfAnyExceptWhiteSpace());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
<Compile Include="ReadOnlySpan\ToArray.cs" />
<Compile Include="ReadOnlySpan\ToString.cs" />
<Compile Include="ReadOnlySpan\ToUpperLower.cs" />
<Compile Include="ReadOnlySpan\WhiteSpace.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="Memory\AsMemory.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,9 @@ public static partial class MemoryExtensions
/// <summary>
/// Indicates whether the specified span contains only white-space characters.
/// </summary>
public static bool IsWhiteSpace(this ReadOnlySpan<char> span)
{
for (int i = 0; i < span.Length; i++)
{
if (!char.IsWhiteSpace(span[i]))
return false;
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsWhiteSpace(this ReadOnlySpan<char> span) =>
!string.SearchValuesStorage.WhiteSpaceChars.ContainsAnyExcept(span);

/// <summary>
/// Returns a value indicating whether the specified <paramref name="value"/> occurs within the <paramref name="span"/>.
Expand All @@ -35,6 +29,16 @@ public static bool Contains(this ReadOnlySpan<char> span, ReadOnlySpan<char> val
return IndexOf(span, value, comparisonType) >= 0;
}

/// <summary>
/// Returns a value indicating whether the specified span contains <see cref="char.IsWhiteSpace(char)">any
/// white-space characters</see>, and returns <see langword="true"/> if found. If not found, returns
/// <see langword="false"/>.
/// </summary>
/// <param name="span">The source span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsAnyWhiteSpace(this ReadOnlySpan<char> span) =>
string.SearchValuesStorage.WhiteSpaceChars.ContainsAny(span);

/// <summary>
/// Determines whether this <paramref name="span"/> and the specified <paramref name="other"/> span have the same characters
/// when compared using the specified <paramref name="comparisonType"/> option.
Expand Down Expand Up @@ -149,6 +153,24 @@ public static int IndexOf(this ReadOnlySpan<char> span, ReadOnlySpan<char> value
}
}

/// <summary>
/// Reports the zero-based index of the first occurrence of <see cref="char.IsWhiteSpace(char)">any white-space
/// characters</see> in the current <paramref name="span"/>, or -1 if not founded.
/// </summary>
/// <param name="span">The source span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfAnyWhiteSpace(this ReadOnlySpan<char> span) =>
string.SearchValuesStorage.WhiteSpaceChars.IndexOfAny(span);

/// <summary>
/// Reports the zero-based index of the first occurrence of <see cref="char.IsWhiteSpace(char)">any
/// non-white-space characters</see> in the current <paramref name="span"/>, or -1 if not founded.
/// </summary>
/// <param name="span">The source span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfAnyExceptWhiteSpace(this ReadOnlySpan<char> span) =>
string.SearchValuesStorage.WhiteSpaceChars.IndexOfAnyExcept(span);

/// <summary>
/// Reports the zero-based index of the last occurrence of the specified <paramref name="value"/> in the current <paramref name="span"/>.
/// </summary>
Expand Down Expand Up @@ -184,6 +206,24 @@ ref MemoryMarshal.GetReference(value),
}
}

/// <summary>
/// Reports the zero-based index of the last occurrence of <see cref="char.IsWhiteSpace(char)">any white-space
/// characters</see> in the current <paramref name="span"/>, or -1 if not founded.
/// </summary>
/// <param name="span">The source span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOfAnyWhiteSpace(this ReadOnlySpan<char> span) =>
string.SearchValuesStorage.WhiteSpaceChars.LastIndexOfAny(span);

/// <summary>
/// Reports the zero-based index of the last occurrence of <see cref="char.IsWhiteSpace(char)">any
/// non-white-space characters</see> in the current <paramref name="span"/>, or -1 if not founded.
/// </summary>
/// <param name="span">The source span.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LastIndexOfAnyExceptWhiteSpace(this ReadOnlySpan<char> span) =>
string.SearchValuesStorage.WhiteSpaceChars.LastIndexOfAnyExcept(span);

/// <summary>
/// Copies the characters from the source span into the destination, converting each character to lowercase,
/// using the casing rules of the specified culture.
Expand Down
Loading