diff --git a/src/CountExceedingBehaviour.cs b/src/CountExceedingBehaviour.cs index eacc395..f89443e 100644 --- a/src/CountExceedingBehaviour.cs +++ b/src/CountExceedingBehaviour.cs @@ -1,17 +1,78 @@ -namespace SpanExtensions +using System; +using System.Globalization; + +namespace SpanExtensions { /// - /// Defines the behaviour of a split operation when there are more split instances than there may be. - /// + /// Defines the behaviour of a split operation when there are more split instances than desired. + /// public enum CountExceedingBehaviour { /// - /// The last element returned will be all the remaining elements appended as one. + /// The last split returned will include all the remaining elements. /// - AppendLastElements, + AppendRemainingElements, /// - /// Every split instance more than permitted will not be returned. - /// - CutLastElements + /// Splits after the desired split count will be cut. + /// + CutRemainingElements + } + + /// + /// Extension methods for . + /// + static class CountExceedingBehaviourExtensions + { + static string? CountExceedingBehaviourInvalidFormat; + + /// + /// Validates whether a specified value is a valid . + /// + /// The to validate. + /// The specified if valid. + /// If is not valid. + 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(); +#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; + } + + /// + /// Determines whether the current instance is . + /// + /// The instance to test. + /// if is ; otherwise. + public static bool IsAppendRemainingElements(this CountExceedingBehaviour countExceedingBehaviour) + { + return countExceedingBehaviour == CountExceedingBehaviour.AppendRemainingElements; + } + + /// + /// Determines whether the current instance is . + /// + /// The instance to test. + /// if is ; otherwise. + public static bool IsCutRemainingElements(this CountExceedingBehaviour countExceedingBehaviour) + { + return countExceedingBehaviour == CountExceedingBehaviour.CutRemainingElements; + } } -} \ No newline at end of file +} diff --git a/src/Enumerators/Split/SpanSplitEnumerator.cs b/src/Enumerators/Split/SpanSplitEnumerator.cs index 0fafb2b..7d16683 100644 --- a/src/Enumerators/Split/SpanSplitEnumerator.cs +++ b/src/Enumerators/Split/SpanSplitEnumerator.cs @@ -10,7 +10,8 @@ namespace SpanExtensions.Enumerators { ReadOnlySpan Span; readonly T Delimiter; - bool enumerationDone; + const int DelimiterLength = 1; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -26,8 +27,8 @@ public SpanSplitEnumerator(ReadOnlySpan source, T delimiter) { Span = source; Delimiter = delimiter; + EnumerationDone = false; Current = default; - enumerationDone = false; } /// @@ -44,22 +45,25 @@ public readonly SpanSplitEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan 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; } } diff --git a/src/Enumerators/Split/SpanSplitStringSplitOptionsEnumerator.cs b/src/Enumerators/Split/SpanSplitStringSplitOptionsEnumerator.cs index 3248861..0b2118c 100644 --- a/src/Enumerators/Split/SpanSplitStringSplitOptionsEnumerator.cs +++ b/src/Enumerators/Split/SpanSplitStringSplitOptionsEnumerator.cs @@ -9,8 +9,10 @@ public ref struct SpanSplitStringSplitOptionsEnumerator { ReadOnlySpan Span; readonly char Delimiter; - readonly StringSplitOptions Options; - bool enumerationDone; + const int DelimiterLength = 1; + readonly bool TrimEntries; + readonly bool RemoveEmptyEntries; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -27,9 +29,10 @@ public SpanSplitStringSplitOptionsEnumerator(ReadOnlySpan source, char del { Span = source; Delimiter = delimiter; - Options = options; + TrimEntries = options.ThrowIfInvalid().IsTrimEntriesSet(); + RemoveEmptyEntries = options.IsRemoveEmptyEntriesSet(); + EnumerationDone = false; Current = default; - enumerationDone = false; } /// @@ -46,49 +49,44 @@ public readonly SpanSplitStringSplitOptionsEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan 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; } } } \ No newline at end of file diff --git a/src/Enumerators/Split/SpanSplitStringSplitOptionsWithCountEnumerator.cs b/src/Enumerators/Split/SpanSplitStringSplitOptionsWithCountEnumerator.cs index 231e568..44731ec 100644 --- a/src/Enumerators/Split/SpanSplitStringSplitOptionsWithCountEnumerator.cs +++ b/src/Enumerators/Split/SpanSplitStringSplitOptionsWithCountEnumerator.cs @@ -9,12 +9,12 @@ public ref struct SpanSplitStringSplitOptionsWithCountEnumerator { ReadOnlySpan Span; readonly char Delimiter; - readonly StringSplitOptions Options; - readonly int Count; + const int DelimiterLength = 1; + readonly bool TrimEntries; + readonly bool RemoveEmptyEntries; readonly CountExceedingBehaviour CountExceedingBehaviour; - int currentCount; - bool enumerationDone; - readonly int CountMinusOne; + int CurrentCount; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -29,17 +29,21 @@ public ref struct SpanSplitStringSplitOptionsWithCountEnumerator /// The maximum number of sub-ReadOnlySpans to split into. /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. - public SpanSplitStringSplitOptionsWithCountEnumerator(ReadOnlySpan source, char delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public SpanSplitStringSplitOptionsWithCountEnumerator(ReadOnlySpan source, char delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { Span = source; Delimiter = delimiter; - Count = count; - CountExceedingBehaviour = countExceedingBehaviour; - Options = options; + CurrentCount = count.ThrowIfNegative(); + TrimEntries = options.ThrowIfInvalid().IsTrimEntriesSet(); + RemoveEmptyEntries = options.IsRemoveEmptyEntriesSet(); + CountExceedingBehaviour = countExceedingBehaviour.ThrowIfInvalid(); + EnumerationDone = count == 0; Current = default; - currentCount = 0; - enumerationDone = false; - CountMinusOne = Math.Max(Count - 1, 0); + + if(count == 1) // special case + { + CurrentCount = 0; + } } /// @@ -56,75 +60,72 @@ public readonly SpanSplitStringSplitOptionsWithCountEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - if(currentCount == Count) + while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached { - return false; - } - int index = span.IndexOf(Delimiter); + int delimiterIndex = Span.IndexOf(Delimiter); - switch(CountExceedingBehaviour) - { - case CountExceedingBehaviour.CutLastElements: - break; - case CountExceedingBehaviour.AppendLastElements: - if(currentCount == CountMinusOne) + if(delimiterIndex == -1 || CurrentCount <= 1) + { + EnumerationDone = true; + + if(CurrentCount != 0 && delimiterIndex != -1 && RemoveEmptyEntries) // skip all empty (after trimming if necessary) entries from the left { - ReadOnlySpan lower = span[..index]; - ReadOnlySpan upper = span[(index + 1)..]; - Span temp = new char[lower.Length + upper.Length]; - lower.CopyTo(temp[..index]); - upper.CopyTo(temp[index..]); - Current = temp; - currentCount++; - return true; + do + { + ReadOnlySpan beforeDelimiter = Span[..delimiterIndex]; + + if(TrimEntries ? beforeDelimiter.IsWhiteSpace() : beforeDelimiter.IsEmpty) + { + Span = Span[(delimiterIndex + DelimiterLength)..]; + delimiterIndex = Span.IndexOf(Delimiter); + + continue; + } + + if(CountExceedingBehaviour.IsCutRemainingElements()) + { + Span = beforeDelimiter; + } + break; + } + while(delimiterIndex != -1); + + Current = Span; + } + else + { + Current = delimiterIndex == -1 || CountExceedingBehaviour.IsAppendRemainingElements() ? Span : Span[..delimiterIndex]; } - break; - default: - throw new InvalidCountExceedingBehaviourException(CountExceedingBehaviour); - } - if(index == -1 || index >= span.Length) - { - enumerationDone = true; - Current = span; - return true; - } - currentCount++; - Current = span[..index]; -#if NET5_0_OR_GREATER - if(Options.HasFlag(StringSplitOptions.TrimEntries)) - { - Current = Current.Trim(); - } -#endif - if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) - { - if(Current.IsEmpty) - { - Span = span[(index + 1)..]; - if(Span.IsEmpty) + 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; + } + + CurrentCount--; return true; } - Span = span[(index + 1)..]; - return true; } } } \ No newline at end of file diff --git a/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs b/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs index e65a913..9bf1cf6 100644 --- a/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs +++ b/src/Enumerators/Split/SpanSplitWithCountEnumerator.cs @@ -6,15 +6,14 @@ namespace SpanExtensions.Enumerators /// Supports iteration over a by splitting it a a specified delimiter of type with an upper limit of splits performed. /// /// The type of elements in the enumerated . - public ref struct SpanSplitWithCountEnumerator where T : IEquatable + public ref struct SpanSplitWithCountEnumerator where T : IEquatable { ReadOnlySpan Span; readonly T Delimiter; - readonly int Count; - readonly CountExceedingBehaviour CountExceedingBehaviour; - int currentCount; - bool enumerationDone; - readonly int CountMinusOne; + const int DelimiterLength = 1; + readonly CountExceedingBehaviour CountExceedingBehaviour; + int CurrentCount; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -28,16 +27,14 @@ namespace SpanExtensions.Enumerators /// An instance of that delimits the various sub-ReadOnlySpans in . /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. - public SpanSplitWithCountEnumerator(ReadOnlySpan source, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public SpanSplitWithCountEnumerator(ReadOnlySpan source, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { Span = source; Delimiter = delimiter; - Count = count; - CountExceedingBehaviour = countExceedingBehaviour; + CurrentCount = count.ThrowIfNegative(); + CountExceedingBehaviour = countExceedingBehaviour.ThrowIfInvalid(); + EnumerationDone = count == 0; Current = default; - currentCount = 0; - enumerationDone = false; - CountMinusOne = Math.Max(Count - 1, 0); } /// @@ -54,46 +51,26 @@ public readonly SpanSplitWithCountEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - if(currentCount == Count) - { - return false; - } - int index = span.IndexOf(Delimiter); - switch(CountExceedingBehaviour) - { - case CountExceedingBehaviour.CutLastElements: - break; - case CountExceedingBehaviour.AppendLastElements: - if(currentCount == CountMinusOne) - { - ReadOnlySpan lower = span[..index]; - ReadOnlySpan upper = span[(index + 1)..]; - Span temp = new T[lower.Length + upper.Length]; - lower.CopyTo(temp[..index]); - upper.CopyTo(temp[index..]); - Current = temp; - currentCount++; - return true; - } - break; - default: - throw new InvalidCountExceedingBehaviourException(CountExceedingBehaviour); - } - if(index == -1 || index >= span.Length) + int delimiterIndex = Span.IndexOf(Delimiter); + + if(delimiterIndex == -1 || CurrentCount == 1) { - enumerationDone = true; - Current = span; + EnumerationDone = true; + + Current = delimiterIndex == -1 || CountExceedingBehaviour.IsAppendRemainingElements() ? Span : Span[..delimiterIndex]; + return true; } - currentCount++; - Current = span[..index]; - Span = span[(index + 1)..]; + + Current = Span[..delimiterIndex]; + Span = Span[(delimiterIndex + DelimiterLength)..]; + + CurrentCount--; return true; } } diff --git a/src/Enumerators/SplitAny/SpanSplitAnyEnumerator.cs b/src/Enumerators/SplitAny/SpanSplitAnyEnumerator.cs index 104361b..cf96c1b 100644 --- a/src/Enumerators/SplitAny/SpanSplitAnyEnumerator.cs +++ b/src/Enumerators/SplitAny/SpanSplitAnyEnumerator.cs @@ -11,7 +11,8 @@ namespace SpanExtensions.Enumerators { ReadOnlySpan Span; readonly ReadOnlySpan Delimiters; - bool enumerationDone; + const int DelimiterLength = 1; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -27,8 +28,8 @@ public SpanSplitAnyEnumerator(ReadOnlySpan source, ReadOnlySpan delimiters { Span = source; Delimiters = delimiters; + EnumerationDone = false; Current = default; - enumerationDone = false; } /// @@ -45,22 +46,25 @@ public readonly SpanSplitAnyEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - int index = span.IndexOfAny(Delimiters); + int delimiterIndex = Span.IndexOfAny(Delimiters); - 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; } } diff --git a/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsEnumerator.cs b/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsEnumerator.cs index e49cd02..1cf2843 100644 --- a/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsEnumerator.cs +++ b/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsEnumerator.cs @@ -9,8 +9,10 @@ public ref struct SpanSplitAnyStringSplitOptionsEnumerator { ReadOnlySpan Span; readonly ReadOnlySpan Delimiters; - readonly StringSplitOptions Options; - bool enumerationDone; + const int DelimiterLength = 1; + readonly bool TrimEntries; + readonly bool RemoveEmptyEntries; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -27,9 +29,10 @@ public SpanSplitAnyStringSplitOptionsEnumerator(ReadOnlySpan source, ReadO { Span = source; Delimiters = delimiters; - Options = options; + TrimEntries = options.ThrowIfInvalid().IsTrimEntriesSet(); + RemoveEmptyEntries = options.IsRemoveEmptyEntriesSet(); + EnumerationDone = false; Current = default; - enumerationDone = false; } /// @@ -46,50 +49,44 @@ public readonly SpanSplitAnyStringSplitOptionsEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - int index = span.IndexOfAny(Delimiters); - - if(index == -1 || index >= span.Length) + while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached { - enumerationDone = true; - Current = span; - return true; - } - Current = span[..index]; + int delimiterIndex = Span.IndexOfAny(Delimiters); -#if NET5_0_OR_GREATER - if(Options.HasFlag(StringSplitOptions.TrimEntries)) - { - Current = Current.Trim(); - } -#endif - if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) - { - if(Current.IsEmpty) + 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; } } } \ No newline at end of file diff --git a/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsWithCountEnumerator.cs b/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsWithCountEnumerator.cs index 40af53a..45af869 100644 --- a/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsWithCountEnumerator.cs +++ b/src/Enumerators/SplitAny/SpanSplitAnyStringSplitOptionsWithCountEnumerator.cs @@ -9,12 +9,12 @@ public ref struct SpanSplitAnyStringSplitOptionsWithCountEnumerator { ReadOnlySpan Span; readonly ReadOnlySpan Delimiters; - readonly StringSplitOptions Options; - readonly int Count; + const int DelimiterLength = 1; + readonly bool TrimEntries; + readonly bool RemoveEmptyEntries; readonly CountExceedingBehaviour CountExceedingBehaviour; - int currentCount; - bool enumerationDone; - readonly int CountMinusOne; + int CurrentCount; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -29,17 +29,21 @@ public ref struct SpanSplitAnyStringSplitOptionsWithCountEnumerator /// The maximum number of sub-ReadOnlySpans to split into. /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. - public SpanSplitAnyStringSplitOptionsWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiters, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public SpanSplitAnyStringSplitOptionsWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiters, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { Span = source; Delimiters = delimiters; - Count = count; - Options = options; - CountExceedingBehaviour = countExceedingBehaviour; + CurrentCount = count.ThrowIfNegative(); + TrimEntries = options.ThrowIfInvalid().IsTrimEntriesSet(); + RemoveEmptyEntries = options.IsRemoveEmptyEntriesSet(); + CountExceedingBehaviour = countExceedingBehaviour.ThrowIfInvalid(); + EnumerationDone = count == 0; Current = default; - currentCount = 0; - enumerationDone = false; - CountMinusOne = Math.Max(Count - 1, 0); + + if(count == 1) // special case + { + CurrentCount = 0; + } } /// @@ -56,75 +60,72 @@ public readonly SpanSplitAnyStringSplitOptionsWithCountEnumerator GetEnumerator( /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - if(currentCount == Count) + while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached { - return false; - } - int index = span.IndexOfAny(Delimiters); + int delimiterIndex = Span.IndexOfAny(Delimiters); - switch(CountExceedingBehaviour) - { - case CountExceedingBehaviour.CutLastElements: - break; - case CountExceedingBehaviour.AppendLastElements: - if(currentCount == CountMinusOne) + if(delimiterIndex == -1 || CurrentCount <= 1) + { + EnumerationDone = true; + + if(CurrentCount != 0 && delimiterIndex != -1 && RemoveEmptyEntries) // skip all empty (after trimming if necessary) entries from the left { - ReadOnlySpan lower = span[..index]; - ReadOnlySpan upper = span[(index + 1)..]; - Span temp = new char[lower.Length + upper.Length]; - lower.CopyTo(temp[..index]); - upper.CopyTo(temp[index..]); - Current = temp; - currentCount++; - return true; + do + { + ReadOnlySpan beforeDelimiter = Span[..delimiterIndex]; + + if(TrimEntries ? beforeDelimiter.IsWhiteSpace() : beforeDelimiter.IsEmpty) + { + Span = Span[(delimiterIndex + DelimiterLength)..]; + delimiterIndex = Span.IndexOfAny(Delimiters); + + continue; + } + + if(CountExceedingBehaviour.IsCutRemainingElements()) + { + Span = beforeDelimiter; + } + break; + } + while(delimiterIndex != -1); + + Current = Span; + } + else + { + Current = delimiterIndex == -1 || CountExceedingBehaviour.IsAppendRemainingElements() ? Span : Span[..delimiterIndex]; } - break; - default: - throw new InvalidCountExceedingBehaviourException(CountExceedingBehaviour); - } - if(index == -1 || index >= span.Length) - { - enumerationDone = true; - Current = span; - return true; - } - currentCount++; - Current = span[..index]; -#if NET5_0_OR_GREATER - if(Options.HasFlag(StringSplitOptions.TrimEntries)) - { - Current = Current.Trim(); - } -#endif - if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) - { - if(Current.IsEmpty) - { - Span = span[(index + 1)..]; - if(Span.IsEmpty) + 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; + } + + CurrentCount--; return true; } - Span = span[(index + 1)..]; - return true; } } } \ No newline at end of file diff --git a/src/Enumerators/SplitAny/SpanSplitAnyWithCountEnumerator.cs b/src/Enumerators/SplitAny/SpanSplitAnyWithCountEnumerator.cs index 79ead32..d7e0a19 100644 --- a/src/Enumerators/SplitAny/SpanSplitAnyWithCountEnumerator.cs +++ b/src/Enumerators/SplitAny/SpanSplitAnyWithCountEnumerator.cs @@ -10,11 +10,10 @@ namespace SpanExtensions.Enumerators { ReadOnlySpan Span; readonly ReadOnlySpan Delimiters; - readonly int Count; + const int DelimiterLength = 1; readonly CountExceedingBehaviour CountExceedingBehaviour; - int currentCount; - bool enumerationDone; - readonly int CountMinusOne; + int CurrentCount; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -28,16 +27,14 @@ namespace SpanExtensions.Enumerators /// A with the instances of that delimit the various sub-ReadOnlySpans in . /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. - public SpanSplitAnyWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiters, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public SpanSplitAnyWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiters, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { Span = source; Delimiters = delimiters; - Count = count; - CountExceedingBehaviour = countExceedingBehaviour; + CurrentCount = count.ThrowIfNegative(); + CountExceedingBehaviour = countExceedingBehaviour.ThrowIfInvalid(); + EnumerationDone = count == 0; Current = default; - currentCount = 0; - enumerationDone = false; - CountMinusOne = Math.Max(Count - 1, 0); } /// @@ -54,46 +51,26 @@ public readonly SpanSplitAnyWithCountEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - if(currentCount == Count) - { - return false; - } - int index = span.IndexOfAny(Delimiters); - switch(CountExceedingBehaviour) - { - case CountExceedingBehaviour.CutLastElements: - break; - case CountExceedingBehaviour.AppendLastElements: - if(currentCount == CountMinusOne) - { - ReadOnlySpan lower = span[..index]; - ReadOnlySpan upper = span[(index + 1)..]; - Span temp = new T[lower.Length + upper.Length]; - lower.CopyTo(temp[..index]); - upper.CopyTo(temp[index..]); - Current = temp; - currentCount++; - return true; - } - break; - default: - throw new InvalidCountExceedingBehaviourException(CountExceedingBehaviour); - } - if(index == -1 || index >= span.Length) + int delimiterIndex = Span.IndexOfAny(Delimiters); + + if(delimiterIndex == -1 || CurrentCount == 1) { - enumerationDone = true; - Current = span; + EnumerationDone = true; + + Current = delimiterIndex == -1 || CountExceedingBehaviour.IsAppendRemainingElements() ? Span : Span[..delimiterIndex]; + return true; } - currentCount++; - Current = span[..index]; - Span = span[(index + 1)..]; + + Current = Span[..delimiterIndex]; + Span = Span[(delimiterIndex + DelimiterLength)..]; + + CurrentCount--; return true; } } diff --git a/src/Enumerators/SplitSequence/SpanSplitSequenceEnumerator.cs b/src/Enumerators/SplitSequence/SpanSplitSequenceEnumerator.cs index 013936d..4dc24c9 100644 --- a/src/Enumerators/SplitSequence/SpanSplitSequenceEnumerator.cs +++ b/src/Enumerators/SplitSequence/SpanSplitSequenceEnumerator.cs @@ -10,7 +10,9 @@ namespace SpanExtensions.Enumerators { ReadOnlySpan Span; readonly ReadOnlySpan Delimiter; - bool enumerationDone; + readonly int DelimiterLength; + readonly bool DelimiterIsEmpty; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -26,8 +28,10 @@ public SpanSplitSequenceEnumerator(ReadOnlySpan source, ReadOnlySpan delim { Span = source; Delimiter = delimiter; + DelimiterLength = Delimiter.Length; + DelimiterIsEmpty = Delimiter.IsEmpty; + EnumerationDone = false; Current = default; - enumerationDone = false; } /// @@ -44,22 +48,25 @@ public readonly SpanSplitSequenceEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - int index = span.IndexOf(Delimiter); + int delimiterIndex = Span.IndexOf(Delimiter); - if(index == -1 || index >= span.Length) + if(delimiterIndex == -1 || DelimiterIsEmpty) { - enumerationDone = true; - Current = span; + EnumerationDone = true; + + Current = Span; + return true; } - Current = span[..index]; - Span = span[(index + Delimiter.Length)..]; + + Current = Span[..delimiterIndex]; + Span = Span[(delimiterIndex + DelimiterLength)..]; + return true; } } diff --git a/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsEnumerator.cs b/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsEnumerator.cs index a7dad72..f7f13ab 100644 --- a/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsEnumerator.cs +++ b/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsEnumerator.cs @@ -9,8 +9,11 @@ public ref struct SpanSplitSequenceStringSplitOptionsEnumerator { ReadOnlySpan Span; readonly ReadOnlySpan Delimiter; - readonly StringSplitOptions Options; - bool enumerationDone; + readonly int DelimiterLength; + readonly bool DelimiterIsEmpty; + readonly bool TrimEntries; + readonly bool RemoveEmptyEntries; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -27,9 +30,12 @@ public SpanSplitSequenceStringSplitOptionsEnumerator(ReadOnlySpan source, { Span = source; Delimiter = delimiter; - Options = options; + DelimiterLength = Delimiter.Length; + DelimiterIsEmpty = Delimiter.IsEmpty; + TrimEntries = options.ThrowIfInvalid().IsTrimEntriesSet(); + RemoveEmptyEntries = options.IsRemoveEmptyEntriesSet(); + EnumerationDone = false; Current = default; - enumerationDone = false; } /// @@ -46,50 +52,44 @@ public readonly SpanSplitSequenceStringSplitOptionsEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - int index = span.IndexOf(Delimiter); - - if(index == -1 || index >= span.Length) + while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached { - enumerationDone = true; - Current = span; - return true; - } - Current = span[..index]; + int delimiterIndex = Span.IndexOf(Delimiter); -#if NET5_0_OR_GREATER - if(Options.HasFlag(StringSplitOptions.TrimEntries)) - { - Current = Current.Trim(); - } -#endif - if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) - { - if(Current.IsEmpty) + if(delimiterIndex == -1 || DelimiterIsEmpty) { - Span = span[(index + Delimiter.Length)..]; - 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 + Delimiter.Length)..]; - return true; } } } \ No newline at end of file diff --git a/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsWithCountEnumerator.cs b/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsWithCountEnumerator.cs index 7eb4ebe..065716b 100644 --- a/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsWithCountEnumerator.cs +++ b/src/Enumerators/SplitSequence/SpanSplitSequenceStringSplitOptionsWithCountEnumerator.cs @@ -9,12 +9,14 @@ public ref struct SpanSplitSequenceStringSplitOptionsWithCountEnumerator { ReadOnlySpan Span; readonly ReadOnlySpan Delimiter; - readonly StringSplitOptions Options; - readonly int Count; + readonly int DelimiterLength; + readonly bool DelimiterIsEmpty; + readonly bool TrimEntries; + readonly bool RemoveEmptyEntries; readonly CountExceedingBehaviour CountExceedingBehaviour; - int currentCount; - bool enumerationDone; - readonly int CountMinusOne; + int CurrentCount; + bool EnumerationDone; + /// /// Gets the element in the collection at the current position of the enumerator. /// @@ -25,20 +27,26 @@ public ref struct SpanSplitSequenceStringSplitOptionsWithCountEnumerator /// /// The to be split. /// An instance of that delimits the various sub-ReadOnlySpans in . - /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The maximum number of sub-ReadOnlySpans to split into. + /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. - public SpanSplitSequenceStringSplitOptionsWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public SpanSplitSequenceStringSplitOptionsWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { Span = source; Delimiter = delimiter; - Count = count; - Options = options; - CountExceedingBehaviour = countExceedingBehaviour; + DelimiterLength = Delimiter.Length; + DelimiterIsEmpty = Delimiter.IsEmpty; + CurrentCount = DelimiterIsEmpty ? 0 : count.ThrowIfNegative(); + TrimEntries = options.ThrowIfInvalid().IsTrimEntriesSet(); + RemoveEmptyEntries = options.IsRemoveEmptyEntriesSet(); + CountExceedingBehaviour = countExceedingBehaviour.ThrowIfInvalid(); + EnumerationDone = count == 0; Current = default; - currentCount = 0; - enumerationDone = false; - CountMinusOne = Math.Max(Count - 1, 0); + + if(count == 1) // special case + { + CurrentCount = 0; + } } /// @@ -55,75 +63,72 @@ public readonly SpanSplitSequenceStringSplitOptionsWithCountEnumerator GetEnumer /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - if(currentCount == Count) + while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached { - return false; - } - int index = span.IndexOf(Delimiter); + int delimiterIndex = Span.IndexOf(Delimiter); - switch(CountExceedingBehaviour) - { - case CountExceedingBehaviour.CutLastElements: - break; - case CountExceedingBehaviour.AppendLastElements: - if(currentCount == CountMinusOne) + if(delimiterIndex == -1 || CurrentCount <= 1) + { + EnumerationDone = true; + + if(CurrentCount != 0 && delimiterIndex != -1 && RemoveEmptyEntries) // skip all empty (after trimming if necessary) entries from the left { - ReadOnlySpan lower = span[..index]; - ReadOnlySpan upper = span[(index + Delimiter.Length)..]; - Span temp = new char[lower.Length + upper.Length]; - lower.CopyTo(temp[..index]); - upper.CopyTo(temp[(index + Delimiter.Length - 1)..]); - Current = temp; - currentCount++; - return true; + do + { + ReadOnlySpan beforeDelimiter = Span[..delimiterIndex]; + + if(TrimEntries ? beforeDelimiter.IsWhiteSpace() : beforeDelimiter.IsEmpty) + { + Span = Span[(delimiterIndex + DelimiterLength)..]; + delimiterIndex = Span.IndexOf(Delimiter); + + continue; + } + + if(CountExceedingBehaviour.IsCutRemainingElements()) + { + Span = beforeDelimiter; + } + break; + } + while(delimiterIndex != -1); + + Current = Span; + } + else + { + Current = delimiterIndex == -1 || CountExceedingBehaviour.IsAppendRemainingElements() || DelimiterIsEmpty ? Span : Span[..delimiterIndex]; } - break; - default: - throw new InvalidCountExceedingBehaviourException(CountExceedingBehaviour); - } - if(index == -1 || index >= span.Length) - { - enumerationDone = true; - Current = span; - return true; - } - currentCount++; - Current = span[..index]; -#if NET5_0_OR_GREATER - if(Options.HasFlag(StringSplitOptions.TrimEntries)) - { - Current = Current.Trim(); - } -#endif - if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries)) - { - if(Current.IsEmpty) - { - Span = span[(index + Delimiter.Length)..]; - if(Span.IsEmpty) + 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; + } + + CurrentCount--; return true; } - Span = span[(index + Delimiter.Length)..]; - return true; } } } \ No newline at end of file diff --git a/src/Enumerators/SplitSequence/SpanSplitSequenceWithCountEnumerator.cs b/src/Enumerators/SplitSequence/SpanSplitSequenceWithCountEnumerator.cs index 2af86d4..5c8274c 100644 --- a/src/Enumerators/SplitSequence/SpanSplitSequenceWithCountEnumerator.cs +++ b/src/Enumerators/SplitSequence/SpanSplitSequenceWithCountEnumerator.cs @@ -10,11 +10,11 @@ namespace SpanExtensions.Enumerators { ReadOnlySpan Span; readonly ReadOnlySpan Delimiter; - readonly int Count; + readonly int DelimiterLength; + readonly bool DelimiterIsEmpty; readonly CountExceedingBehaviour CountExceedingBehaviour; - int currentCount; - bool enumerationDone; - readonly int CountMinusOne; + int CurrentCount; + bool EnumerationDone; /// /// Gets the element in the collection at the current position of the enumerator. @@ -28,16 +28,16 @@ namespace SpanExtensions.Enumerators /// An instance of that delimits the various sub-ReadOnlySpans in . /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. - public SpanSplitSequenceWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public SpanSplitSequenceWithCountEnumerator(ReadOnlySpan source, ReadOnlySpan delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { Span = source; Delimiter = delimiter; - Count = count; - CountExceedingBehaviour = countExceedingBehaviour; + DelimiterLength = Delimiter.Length; + DelimiterIsEmpty = Delimiter.IsEmpty; + CurrentCount = DelimiterIsEmpty ? 1 : count.ThrowIfNegative(); + CountExceedingBehaviour = countExceedingBehaviour.ThrowIfInvalid(); + EnumerationDone = count == 0; Current = default; - currentCount = 0; - enumerationDone = false; - CountMinusOne = Math.Max(Count - 1, 0); } /// @@ -54,46 +54,26 @@ public readonly SpanSplitSequenceWithCountEnumerator GetEnumerator() /// if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. public bool MoveNext() { - if(enumerationDone) + if(EnumerationDone) { return false; } - ReadOnlySpan span = Span; - if(currentCount == Count) - { - return false; - } - int index = span.IndexOf(Delimiter); - switch(CountExceedingBehaviour) - { - case CountExceedingBehaviour.CutLastElements: - break; - case CountExceedingBehaviour.AppendLastElements: - if(currentCount == CountMinusOne) - { - ReadOnlySpan lower = span[..index]; - ReadOnlySpan upper = span[(index + Delimiter.Length)..]; - Span temp = new T[lower.Length + upper.Length]; - lower.CopyTo(temp[..index]); - upper.CopyTo(temp[(index + Delimiter.Length - 1)..]); - Current = temp; - currentCount++; - return true; - } - break; - default: - throw new InvalidCountExceedingBehaviourException(CountExceedingBehaviour); - } - if(index == -1 || index >= span.Length) + int delimiterIndex = Span.IndexOf(Delimiter); + + if(delimiterIndex == -1 || CurrentCount == 1) { - enumerationDone = true; - Current = span; + EnumerationDone = true; + + Current = delimiterIndex == -1 || CountExceedingBehaviour.IsAppendRemainingElements() || DelimiterIsEmpty ? Span : Span[..delimiterIndex]; + return true; } - currentCount++; - Current = span[..index]; - Span = span[(index + Delimiter.Length)..]; + + Current = Span[..delimiterIndex]; + Span = Span[(delimiterIndex + DelimiterLength)..]; + + CurrentCount--; return true; } } diff --git a/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Split.cs b/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Split.cs index 501a28e..a819c4c 100644 --- a/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Split.cs +++ b/src/Extensions/ReadOnlySpan/ReadOnlySpanExtensions.Split.cs @@ -30,7 +30,7 @@ public static SpanSplitEnumerator Split(this ReadOnlySpan source, T del /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitWithCountEnumerator Split(this ReadOnlySpan source, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) where T : IEquatable + public static SpanSplitWithCountEnumerator Split(this ReadOnlySpan source, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable { return new SpanSplitWithCountEnumerator(source, delimiter, count, countExceedingBehaviour); } @@ -56,7 +56,7 @@ public static SpanSplitStringSplitOptionsEnumerator Split(this ReadOnlySpanA bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitStringSplitOptionsWithCountEnumerator Split(this ReadOnlySpan source, char delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitStringSplitOptionsWithCountEnumerator Split(this ReadOnlySpan source, char delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { return new SpanSplitStringSplitOptionsWithCountEnumerator(source, delimiter, count, options, countExceedingBehaviour); } @@ -82,7 +82,7 @@ public static SpanSplitAnyEnumerator SplitAny(this ReadOnlySpan source, /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitAnyWithCountEnumerator SplitAny(this ReadOnlySpan source, ReadOnlySpan delimiters, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) where T : IEquatable + public static SpanSplitAnyWithCountEnumerator SplitAny(this ReadOnlySpan source, ReadOnlySpan delimiters, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable { return new SpanSplitAnyWithCountEnumerator(source, delimiters, count, countExceedingBehaviour); } @@ -108,7 +108,7 @@ public static SpanSplitAnyStringSplitOptionsEnumerator SplitAny(this ReadOnlySpa /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitAnyStringSplitOptionsWithCountEnumerator SplitAny(this ReadOnlySpan source, ReadOnlySpan delimiters, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitAnyStringSplitOptionsWithCountEnumerator SplitAny(this ReadOnlySpan source, ReadOnlySpan delimiters, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { return new SpanSplitAnyStringSplitOptionsWithCountEnumerator(source, delimiters, count, options, countExceedingBehaviour); } @@ -134,7 +134,7 @@ public static SpanSplitSequenceEnumerator Split(this ReadOnlySpan sourc /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitSequenceWithCountEnumerator Split(this ReadOnlySpan source, ReadOnlySpan delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) where T : IEquatable + public static SpanSplitSequenceWithCountEnumerator Split(this ReadOnlySpan source, ReadOnlySpan delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable { return new SpanSplitSequenceWithCountEnumerator(source, delimiter, count, countExceedingBehaviour); } @@ -160,7 +160,7 @@ public static SpanSplitSequenceStringSplitOptionsEnumerator Split(this ReadOnlyS /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitSequenceStringSplitOptionsWithCountEnumerator Split(this ReadOnlySpan source, ReadOnlySpan delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitSequenceStringSplitOptionsWithCountEnumerator Split(this ReadOnlySpan source, ReadOnlySpan delimiter, int count, StringSplitOptions options, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { return new SpanSplitSequenceStringSplitOptionsWithCountEnumerator(source, delimiter, count, options, countExceedingBehaviour); } diff --git a/src/Extensions/Span/SpanExtensions.Split.cs b/src/Extensions/Span/SpanExtensions.Split.cs index 6a2f427..8247e40 100644 --- a/src/Extensions/Span/SpanExtensions.Split.cs +++ b/src/Extensions/Span/SpanExtensions.Split.cs @@ -30,7 +30,7 @@ public static SpanSplitEnumerator Split(this Span source, T delimiter) /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitWithCountEnumerator Split(this Span source, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) where T : IEquatable + public static SpanSplitWithCountEnumerator Split(this Span source, T delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable { return new SpanSplitWithCountEnumerator(source, delimiter, count, countExceedingBehaviour); } @@ -56,7 +56,7 @@ public static SpanSplitStringSplitOptionsEnumerator Split(this Span source /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitStringSplitOptionsWithCountEnumerator Split(this Span source, char delimiter, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitStringSplitOptionsWithCountEnumerator Split(this Span source, char delimiter, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { return new SpanSplitStringSplitOptionsWithCountEnumerator(source, delimiter, count, options, countExceedingBehaviour); } @@ -78,11 +78,11 @@ public static SpanSplitAnyEnumerator SplitAny(this Span source, ReadOnl /// /// The type of elements in the . /// The to be split. - /// The maximum number of sub-ReadOnlySpans to split into. /// A , that delimit the various sub-ReadOnlySpans in . + /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitAnyWithCountEnumerator SplitAny(this Span source, ReadOnlySpan delimiters, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) where T : IEquatable + public static SpanSplitAnyWithCountEnumerator SplitAny(this Span source, ReadOnlySpan delimiters, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable { return new SpanSplitAnyWithCountEnumerator(source, delimiters, count, countExceedingBehaviour); } @@ -104,11 +104,11 @@ public static SpanSplitAnyStringSplitOptionsEnumerator SplitAny(this Span /// /// The to be split. /// A , that delimit the various sub-ReadOnlySpans in . - /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The maximum number of sub-ReadOnlySpans to split into. + /// A bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitAnyStringSplitOptionsWithCountEnumerator SplitAny(this Span source, ReadOnlySpan delimiters, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitAnyStringSplitOptionsWithCountEnumerator SplitAny(this Span source, ReadOnlySpan delimiters, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { return new SpanSplitAnyStringSplitOptionsWithCountEnumerator(source, delimiters, count, options, countExceedingBehaviour); } @@ -134,7 +134,7 @@ public static SpanSplitSequenceEnumerator Split(this Span source, ReadO /// The maximum number of sub-ReadOnlySpans to split into. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitSequenceWithCountEnumerator Split(this Span source, ReadOnlySpan delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) where T : IEquatable + public static SpanSplitSequenceWithCountEnumerator Split(this Span source, ReadOnlySpan delimiter, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) where T : IEquatable { return new SpanSplitSequenceWithCountEnumerator(source, delimiter, count, countExceedingBehaviour); } @@ -160,7 +160,7 @@ public static SpanSplitSequenceStringSplitOptionsEnumerator Split(this SpanA bitwise combination of the enumeration values that specifies whether to trim results and include empty results. /// The handling of the instances more than count. /// An instance of the ref struct , which works the same way as every does and can be used in a foreach construct. - public static SpanSplitSequenceStringSplitOptionsWithCountEnumerator Split(this Span source, ReadOnlySpan delimiter, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendLastElements) + public static SpanSplitSequenceStringSplitOptionsWithCountEnumerator Split(this Span source, ReadOnlySpan delimiter, StringSplitOptions options, int count, CountExceedingBehaviour countExceedingBehaviour = CountExceedingBehaviour.AppendRemainingElements) { return new SpanSplitSequenceStringSplitOptionsWithCountEnumerator(source, delimiter, count, options, countExceedingBehaviour); } diff --git a/src/InternalExtensions.cs b/src/InternalExtensions.cs new file mode 100644 index 0000000..dfe3e43 --- /dev/null +++ b/src/InternalExtensions.cs @@ -0,0 +1,140 @@ +using System; +#if NETCOREAPP3_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif + +namespace SpanExtensions +{ + static class InternalExtensions + { +#if NETCOREAPP3_0_OR_GREATER + /// + /// Throws an if is negative. + /// + /// The argument to validate as non-negative. + /// The name of the parameter with which corresponds. + /// . + public static int ThrowIfNegative(this int value, [CallerArgumentExpression(nameof(value))] string? paramName = null) + { +#if NET8_0_OR_GREATER + ArgumentOutOfRangeException.ThrowIfNegative(value, paramName); +#else + if(value < 0) + { + throw new ArgumentOutOfRangeException(paramName, value, $"{paramName} ('{value}') must be a non-negative value."); + } +#endif + + return value; + } +#else + /// + /// Throws an if is negative. + /// + /// The argument to validate as non-negative. + /// . + public static int ThrowIfNegative(this int value) + { + if(value < 0) + { +#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one + throw new ArgumentOutOfRangeException(null, value, $" ('{value}') must be a non-negative value."); +#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one + } + + return value; + } +#endif + +#if NETCOREAPP3_0_OR_GREATER + /// + /// Throws an if is not a valid flag. + /// + /// The argument to validate as a valid flag. + /// The name of the parameter with which corresponds. + /// . + public static StringSplitOptions ThrowIfInvalid(this StringSplitOptions options, [CallerArgumentExpression(nameof(options))] string? paramName = null) + { + if((options & ~CombinationOfAllValidStringSplitOptions) != 0) + { + throw new ArgumentException("Value of flag is invalid.", paramName); + } + + return options; + } +#else + /// + /// Throws an if is not a valid flag. + /// + /// The argument to validate as a valid flag. + /// . + public static StringSplitOptions ThrowIfInvalid(this StringSplitOptions options) + { + if((options & ~CombinationOfAllValidStringSplitOptions) != 0) + { + throw new ArgumentException("Value of flag is invalid."); + } + + return options; + } +#endif + + /// + /// Determines whether the bit field is set in the current instance. + /// + /// The instance to test. + /// if has the bit field set; otherwise. + public static bool IsRemoveEmptyEntriesSet(this StringSplitOptions options) + { + return options.HasFlag(StringSplitOptions.RemoveEmptyEntries); + } + +#if NET5_0_OR_GREATER + /// + /// Determines whether the bit field is set in the current instance. + /// + /// + /// In runtimes before .NET 5 this always returns . + /// + /// The instance to test. + /// if has the bit field set; otherwise. + public static bool IsTrimEntriesSet(this StringSplitOptions options) + { + return options.HasFlag(StringSplitOptions.TrimEntries); + } +#else + /// + /// Always returns false. + /// + /// The instance to test. + /// . + public static bool IsTrimEntriesSet(this StringSplitOptions options) + { + return false; + } +#endif + + /// + /// Gets all values of an enum and applies the or operation on them. + /// + /// The target enum type. + /// The combination of all valid enum values. + public static T GetCombinationOfAllValidFlags() where T : struct, Enum + { +#if NET5_0_OR_GREATER + T[] flags = Enum.GetValues(); +#else + T[] flags = (T[])Enum.GetValues(typeof(T)); +#endif + + int combination = 0; + foreach(T flag in flags) + { + combination |= (int)(object)flag; + } + return (T)(object)combination; + } + + static readonly StringSplitOptions CombinationOfAllValidStringSplitOptions = GetCombinationOfAllValidFlags(); + } +} diff --git a/src/InvalidCountExceedingBehaviourException.cs b/src/InvalidCountExceedingBehaviourException.cs deleted file mode 100644 index 75eed7e..0000000 --- a/src/InvalidCountExceedingBehaviourException.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; - -namespace SpanExtensions -{ - /// - /// The Exception that is thrown for an undefined enumeration value of . - /// - [Serializable] - public class InvalidCountExceedingBehaviourException : Exception - { - /// - /// Initializes a new instance of the class with a system-supplied message. - /// - /// The invalid . - public InvalidCountExceedingBehaviourException(CountExceedingBehaviour countExceedingBehaviour) : - base($"CountExceedingBehaviour with ID '{(int) countExceedingBehaviour} is not defined. CountExceedingBehaviour only defines {GetCountExceedingBehaviourNamesListed()}.") - { - - } - - static string GetCountExceedingBehaviourNamesListed() - { - string[] countExceedingBehaviourNames = Array.Empty(); -#if NET5_0_OR_GREATER - countExceedingBehaviourNames = Enum.GetNames(); -#else - countExceedingBehaviourNames = (string[]) Enum.GetNames(typeof(CountExceedingBehaviour)); -#endif - switch(countExceedingBehaviourNames.Length) - { - case 0: - return ""; - case 1: - return countExceedingBehaviourNames[0]; - default: - string first = countExceedingBehaviourNames[0]; - string end = string.Join(',', countExceedingBehaviourNames, 1, countExceedingBehaviourNames.Length - 1); - return $"{first} and {end}"; - } - - } - } -} \ No newline at end of file