// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.Contracts; using System.Runtime.CompilerServices; namespace CommunityToolkit.HighPerformance.Enumerables; /// /// A that tokenizes a given instance. /// /// The type of items to enumerate. [EditorBrowsable(EditorBrowsableState.Never)] public ref struct ReadOnlySpanTokenizer where T : IEquatable { /// /// The source instance. /// private readonly ReadOnlySpan span; /// /// The separator item to use. /// private readonly T separator; /// /// The current initial offset. /// private int start; /// /// The current final offset. /// private int end; /// /// Initializes a new instance of the struct. /// /// The source instance. /// The separator item to use. [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpanTokenizer(ReadOnlySpan span, T separator) { this.span = span; this.separator = separator; this.start = 0; this.end = -1; } /// /// Implements the duck-typed method. /// /// An instance targeting the current value. [Pure] [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly ReadOnlySpanTokenizer GetEnumerator() => this; /// /// Implements the duck-typed method. /// /// whether a new element is available, otherwise [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { int newEnd = this.end + 1; int length = this.span.Length; // Additional check if the separator is not the last character if (newEnd <= length) { this.start = newEnd; // We need to call this extension explicitly or the extension method resolution rules for the C# compiler // will end up picking CommunityToolkit.HighPerformance.ReadOnlySpanExtensions.IndexOf instead, even // though the latter takes the parameter via a readonly reference. This is because the "in" modifier is // implicit, which makes the signature compatible, and because extension methods are matched in such a // way that methods "closest" to where they're used are preferred. Since this type shares the same root // namespace, this makes that extension a better match, so that it overrides the MemoryExtensions one. // This is not a problem for consumers of this package, as their code would be outside of the // CommunityToolkit.HighPerformance namespace, so both extensions would be "equally distant", so that // when they're both in scope it will be possible to choose which one to use by adding an explicit "in". int index = System.MemoryExtensions.IndexOf(this.span.Slice(newEnd), this.separator); // Extract the current subsequence if (index >= 0) { this.end = newEnd + index; return true; } this.end = length; return true; } return false; } /// /// Gets the duck-typed property. /// public readonly ReadOnlySpan Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.span.Slice(this.start, this.end - this.start); } }