Skip to content
Merged
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
79 changes: 70 additions & 9 deletions src/CountExceedingBehaviour.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,78 @@
namespace SpanExtensions
using System;
using System.Globalization;

namespace SpanExtensions
{
/// <summary>
/// Defines the behaviour of a split operation when there are more split instances than there may be.
/// </summary>
/// Defines the behaviour of a split operation when there are more split instances than desired.
/// </summary>
public enum CountExceedingBehaviour
{
/// <summary>
/// The last element returned will be all the remaining elements appended as one.
/// The last split returned will include all the remaining elements.
/// </summary>
AppendLastElements,
AppendRemainingElements,
/// <summary>
/// Every split instance more than permitted will not be returned.
/// </summary>
CutLastElements
/// Splits after the desired split count will be cut.
/// </summary>
CutRemainingElements
}

/// <summary>
/// Extension methods for <see cref="CountExceedingBehaviour"/>.
/// </summary>
static class CountExceedingBehaviourExtensions
{
static string? CountExceedingBehaviourInvalidFormat;

/// <summary>
/// Validates whether a specified value is a valid <see cref="CountExceedingBehaviour"/>.
/// </summary>
/// <param name="countExceedingBehaviour">The <see cref="CountExceedingBehaviour"/> to validate.</param>
/// <returns>The specified <see cref="CountExceedingBehaviour"/> if valid.</returns>
/// <exception cref="ArgumentException">If <paramref name="countExceedingBehaviour"/> is not valid.</exception>
public static CountExceedingBehaviour ThrowIfInvalid(this CountExceedingBehaviour countExceedingBehaviour)
{
#if NET5_0_OR_GREATER
if(!Enum.IsDefined(countExceedingBehaviour))
#else
if(!Enum.IsDefined(typeof(CountExceedingBehaviour), countExceedingBehaviour))
#endif
{
if(CountExceedingBehaviourInvalidFormat == null)
{
#if NET5_0_OR_GREATER
string[] names = Enum.GetNames<CountExceedingBehaviour>();
#else
string[] names = Enum.GetNames(typeof(CountExceedingBehaviour));
#endif
CountExceedingBehaviourInvalidFormat = $"{nameof(CountExceedingBehaviour)} doesn't define an option with the value '{{0}}'. Valid values are {string.Join(", ", names)}.";
}

throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, CountExceedingBehaviourInvalidFormat, (int)countExceedingBehaviour), nameof(countExceedingBehaviour));
}

return countExceedingBehaviour;
}

/// <summary>
/// Determines whether the current instance is <see cref="CountExceedingBehaviour.AppendRemainingElements"/>.
/// </summary>
/// <param name="countExceedingBehaviour">The <see cref="CountExceedingBehaviour"/> instance to test.</param>
/// <returns><see langword="true"/> if <paramref name="countExceedingBehaviour"/> is <see cref="CountExceedingBehaviour.AppendRemainingElements"/>; <see langword="false"/> otherwise.</returns>
public static bool IsAppendRemainingElements(this CountExceedingBehaviour countExceedingBehaviour)
{
return countExceedingBehaviour == CountExceedingBehaviour.AppendRemainingElements;
}

/// <summary>
/// Determines whether the current instance is <see cref="CountExceedingBehaviour.CutRemainingElements"/>.
/// </summary>
/// <param name="countExceedingBehaviour">The <see cref="CountExceedingBehaviour"/> instance to test.</param>
/// <returns><see langword="true"/> if <paramref name="countExceedingBehaviour"/> is <see cref="CountExceedingBehaviour.CutRemainingElements"/>; <see langword="false"/> otherwise.</returns>
public static bool IsCutRemainingElements(this CountExceedingBehaviour countExceedingBehaviour)
{
return countExceedingBehaviour == CountExceedingBehaviour.CutRemainingElements;
}
}
}
}
24 changes: 14 additions & 10 deletions src/Enumerators/Split/SpanSplitEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ namespace SpanExtensions.Enumerators
{
ReadOnlySpan<T> Span;
readonly T Delimiter;
bool enumerationDone;
const int DelimiterLength = 1;
bool EnumerationDone;

/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
Expand All @@ -26,8 +27,8 @@ public SpanSplitEnumerator(ReadOnlySpan<T> source, T delimiter)
{
Span = source;
Delimiter = delimiter;
EnumerationDone = false;
Current = default;
enumerationDone = false;
}

/// <summary>
Expand All @@ -44,22 +45,25 @@ public readonly SpanSplitEnumerator<T> GetEnumerator()
/// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element; <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
public bool MoveNext()
{
if(enumerationDone)
if(EnumerationDone)
{
return false;
}

ReadOnlySpan<T> span = Span;
int index = span.IndexOf(Delimiter);
int delimiterIndex = Span.IndexOf(Delimiter);

if(index == -1 || index >= span.Length)
if(delimiterIndex == -1)
{
enumerationDone = true;
Current = span;
EnumerationDone = true;

Current = Span;

return true;
}
Current = span[..index];
Span = span[(index + 1)..];

Current = Span[..delimiterIndex];
Span = Span[(delimiterIndex + DelimiterLength)..];

return true;
}
}
Expand Down
64 changes: 31 additions & 33 deletions src/Enumerators/Split/SpanSplitStringSplitOptionsEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ public ref struct SpanSplitStringSplitOptionsEnumerator
{
ReadOnlySpan<char> Span;
readonly char Delimiter;
readonly StringSplitOptions Options;
bool enumerationDone;
const int DelimiterLength = 1;
readonly bool TrimEntries;
readonly bool RemoveEmptyEntries;
bool EnumerationDone;

/// <summary>
/// Gets the element in the collection at the current position of the enumerator.
Expand All @@ -27,9 +29,10 @@ public SpanSplitStringSplitOptionsEnumerator(ReadOnlySpan<char> source, char del
{
Span = source;
Delimiter = delimiter;
Options = options;
TrimEntries = options.ThrowIfInvalid().IsTrimEntriesSet();
RemoveEmptyEntries = options.IsRemoveEmptyEntriesSet();
EnumerationDone = false;
Current = default;
enumerationDone = false;
}

/// <summary>
Expand All @@ -46,49 +49,44 @@ public readonly SpanSplitStringSplitOptionsEnumerator GetEnumerator()
/// <returns><see langword="true"/> if the enumerator was successfully advanced to the next element; <see langword="false"/> if the enumerator has passed the end of the collection.</returns>
public bool MoveNext()
{
if(enumerationDone)
if(EnumerationDone)
{
return false;
}

ReadOnlySpan<char> span = Span;
int index = span.IndexOf(Delimiter);

if(index == -1 || index >= span.Length)
{
enumerationDone = true;
Current = span;
return true;
}
Current = span[..index];
#if NET5_0_OR_GREATER
if(Options.HasFlag(StringSplitOptions.TrimEntries))
while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached
{
Current = Current.Trim();
}
#endif
if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries))
{
if(Current.IsEmpty)
int delimiterIndex = Span.IndexOf(Delimiter);

if(delimiterIndex == -1)
{
Span = span[(index + 1)..];
if(Span.IsEmpty)
EnumerationDone = true;

Current = Span;

if(TrimEntries)
{
enumerationDone = true;
return false;
Current = Current.Trim();
}
return MoveNext();

return !(RemoveEmptyEntries && Current.IsEmpty);
}

Span = span[(index + 1)..];
if(Span.IsEmpty)
Current = Span[..delimiterIndex];
Span = Span[(delimiterIndex + DelimiterLength)..];

if(TrimEntries)
{
enumerationDone = true;
Current = Current.Trim();
}

if(RemoveEmptyEntries && Current.IsEmpty)
{
continue;
}

return true;
}
Span = span[(index + 1)..];
return true;
}
}
}
Loading