// 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.Diagnostics; using System.Runtime.CompilerServices; namespace System.Buffers { #if __MonoCS__ public ref partial struct SequenceReader<T> where T : IEquatable<T> #else public ref partial struct SequenceReader<T> where T : unmanaged, IEquatable<T> #endif { /// <summary> /// Try to read everything up to the given <paramref name="delimiter"/>. /// </summary> /// <param name="span">The read data, if any.</param> /// <param name="delimiter">The delimiter to look for.</param> /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param> /// <returns>True if the <paramref name="delimiter"/> was found.</returns> public bool TryReadTo(out ReadOnlySpan<T> span, T delimiter, bool advancePastDelimiter = true) { ReadOnlySpan<T> remaining = UnreadSpan; int index = remaining.IndexOf(delimiter); if (index != -1) { span = index == 0 ? default : remaining.Slice(0, index); AdvanceCurrentSpan(index + (advancePastDelimiter ? 1 : 0)); return true; } return TryReadToSlow(out span, delimiter, advancePastDelimiter); } private bool TryReadToSlow(out ReadOnlySpan<T> span, T delimiter, bool advancePastDelimiter) { if (!TryReadToInternal(out ReadOnlySequence<T> sequence, delimiter, advancePastDelimiter, CurrentSpan.Length - CurrentSpanIndex)) { span = default; return false; } span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray(); return true; } /// <summary> /// Try to read everything up to the given <paramref name="delimiter"/>, ignoring delimiters that are /// preceded by <paramref name="delimiterEscape"/>. /// </summary> /// <param name="span">The read data, if any.</param> /// <param name="delimiter">The delimiter to look for.</param> /// <param name="delimiterEscape">If found prior to <paramref name="delimiter"/> it will skip that occurrence.</param> /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param> /// <returns>True if the <paramref name="delimiter"/> was found.</returns> public bool TryReadTo(out ReadOnlySpan<T> span, T delimiter, T delimiterEscape, bool advancePastDelimiter = true) { ReadOnlySpan<T> remaining = UnreadSpan; int index = remaining.IndexOf(delimiter); if ((index > 0 && !remaining[index - 1].Equals(delimiterEscape)) || index == 0) { span = remaining.Slice(0, index); AdvanceCurrentSpan(index + (advancePastDelimiter ? 1 : 0)); return true; } // This delimiter might be skipped, go down the slow path return TryReadToSlow(out span, delimiter, delimiterEscape, index, advancePastDelimiter); } private bool TryReadToSlow(out ReadOnlySpan<T> span, T delimiter, T delimiterEscape, int index, bool advancePastDelimiter) { if (!TryReadToSlow(out ReadOnlySequence<T> sequence, delimiter, delimiterEscape, index, advancePastDelimiter)) { span = default; return false; } Debug.Assert(sequence.Length > 0); span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray(); return true; } private bool TryReadToSlow(out ReadOnlySequence<T> sequence, T delimiter, T delimiterEscape, int index, bool advancePastDelimiter) { SequenceReader<T> copy = this; ReadOnlySpan<T> remaining = UnreadSpan; bool priorEscape = false; do { if (index >= 0) { if (index == 0 && priorEscape) { // We were in the escaped state, so skip this delimiter priorEscape = false; Advance(index + 1); remaining = UnreadSpan; goto Continue; } else if (index > 0 && remaining[index - 1].Equals(delimiterEscape)) { // This delimiter might be skipped // Count our escapes int escapeCount = 1; int i = index - 2; for (; i >= 0; i--) { if (!remaining[i].Equals(delimiterEscape)) break; } if (i < 0 && priorEscape) { // Started and ended with escape, increment once more escapeCount++; } escapeCount += index - 2 - i; if ((escapeCount & 1) != 0) { // An odd escape count means we're currently escaped, // skip the delimiter and reset escaped state. Advance(index + 1); priorEscape = false; remaining = UnreadSpan; goto Continue; } } // Found the delimiter. Move to it, slice, then move past it. AdvanceCurrentSpan(index); sequence = Sequence.Slice(copy.Position, Position); if (advancePastDelimiter) { Advance(1); } return true; } else { // No delimiter, need to check the end of the span for odd number of escapes then advance if (remaining.Length > 0 && remaining[remaining.Length - 1].Equals(delimiterEscape)) { int escapeCount = 1; int i = remaining.Length - 2; for (; i >= 0; i--) { if (!remaining[i].Equals(delimiterEscape)) break; } escapeCount += remaining.Length - 2 - i; if (i < 0 && priorEscape) priorEscape = (escapeCount & 1) == 0; // equivalent to incrementing escapeCount before setting priorEscape else priorEscape = (escapeCount & 1) != 0; } else { priorEscape = false; } } // Nothing in the current span, move to the end, checking for the skip delimiter AdvanceCurrentSpan(remaining.Length); remaining = CurrentSpan; Continue: index = remaining.IndexOf(delimiter); } while (!End); // Didn't find anything, reset our original state. this = copy; sequence = default; return false; } /// <summary> /// Try to read everything up to the given <paramref name="delimiter"/>. /// </summary> /// <param name="sequence">The read data, if any.</param> /// <param name="delimiter">The delimiter to look for.</param> /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param> /// <returns>True if the <paramref name="delimiter"/> was found.</returns> public bool TryReadTo(out ReadOnlySequence<T> sequence, T delimiter, bool advancePastDelimiter = true) { return TryReadToInternal(out sequence, delimiter, advancePastDelimiter); } private bool TryReadToInternal(out ReadOnlySequence<T> sequence, T delimiter, bool advancePastDelimiter, int skip = 0) { Debug.Assert(skip >= 0); SequenceReader<T> copy = this; if (skip > 0) Advance(skip); ReadOnlySpan<T> remaining = UnreadSpan; while (_moreData) { int index = remaining.IndexOf(delimiter); if (index != -1) { // Found the delimiter. Move to it, slice, then move past it. if (index > 0) { AdvanceCurrentSpan(index); } sequence = Sequence.Slice(copy.Position, Position); if (advancePastDelimiter) { Advance(1); } return true; } AdvanceCurrentSpan(remaining.Length); remaining = CurrentSpan; } // Didn't find anything, reset our original state. this = copy; sequence = default; return false; } /// <summary> /// Try to read everything up to the given <paramref name="delimiter"/>, ignoring delimiters that are /// preceded by <paramref name="delimiterEscape"/>. /// </summary> /// <param name="sequence">The read data, if any.</param> /// <param name="delimiter">The delimiter to look for.</param> /// <param name="delimiterEscape">If found prior to <paramref name="delimiter"/> it will skip that occurrence.</param> /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param> /// <returns>True if the <paramref name="delimiter"/> was found.</returns> public bool TryReadTo(out ReadOnlySequence<T> sequence, T delimiter, T delimiterEscape, bool advancePastDelimiter = true) { SequenceReader<T> copy = this; ReadOnlySpan<T> remaining = UnreadSpan; bool priorEscape = false; while (_moreData) { int index = remaining.IndexOf(delimiter); if (index != -1) { if (index == 0 && priorEscape) { // We were in the escaped state, so skip this delimiter priorEscape = false; Advance(index + 1); remaining = UnreadSpan; continue; } else if (index > 0 && remaining[index - 1].Equals(delimiterEscape)) { // This delimiter might be skipped // Count our escapes int escapeCount = 0; for (int i = index; i > 0 && remaining[i - 1].Equals(delimiterEscape); i--, escapeCount++) ; if (escapeCount == index && priorEscape) { // Started and ended with escape, increment once more escapeCount++; } priorEscape = false; if ((escapeCount & 1) != 0) { // Odd escape count means we're in the escaped state, so skip this delimiter Advance(index + 1); remaining = UnreadSpan; continue; } } // Found the delimiter. Move to it, slice, then move past it. if (index > 0) { Advance(index); } sequence = Sequence.Slice(copy.Position, Position); if (advancePastDelimiter) { Advance(1); } return true; } // No delimiter, need to check the end of the span for odd number of escapes then advance { int escapeCount = 0; for (int i = remaining.Length; i > 0 && remaining[i - 1].Equals(delimiterEscape); i--, escapeCount++) ; if (priorEscape && escapeCount == remaining.Length) { escapeCount++; } priorEscape = escapeCount % 2 != 0; } // Nothing in the current span, move to the end, checking for the skip delimiter Advance(remaining.Length); remaining = CurrentSpan; } // Didn't find anything, reset our original state. this = copy; sequence = default; return false; } /// <summary> /// Try to read everything up to the given <paramref name="delimiters"/>. /// </summary> /// <param name="span">The read data, if any.</param> /// <param name="delimiters">The delimiters to look for.</param> /// <param name="advancePastDelimiter">True to move past the first found instance of any of the given <paramref name="delimiters"/>.</param> /// <returns>True if any of the <paramref name="delimiters"/> were found.</returns> [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryReadToAny(out ReadOnlySpan<T> span, ReadOnlySpan<T> delimiters, bool advancePastDelimiter = true) { ReadOnlySpan<T> remaining = UnreadSpan; int index = delimiters.Length == 2 ? remaining.IndexOfAny(delimiters[0], delimiters[1]) : remaining.IndexOfAny(delimiters); if (index != -1) { span = remaining.Slice(0, index); Advance(index + (advancePastDelimiter ? 1 : 0)); return true; } return TryReadToAnySlow(out span, delimiters, advancePastDelimiter); } private bool TryReadToAnySlow(out ReadOnlySpan<T> span, ReadOnlySpan<T> delimiters, bool advancePastDelimiter) { if (!TryReadToAnyInternal(out ReadOnlySequence<T> sequence, delimiters, advancePastDelimiter, CurrentSpan.Length - CurrentSpanIndex)) { span = default; return false; } span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray(); return true; } /// <summary> /// Try to read everything up to the given <paramref name="delimiters"/>. /// </summary> /// <param name="sequence">The read data, if any.</param> /// <param name="delimiters">The delimiters to look for.</param> /// <param name="advancePastDelimiter">True to move past the first found instance of any of the given <paramref name="delimiters"/>.</param> /// <returns>True if any of the <paramref name="delimiters"/> were found.</returns> public bool TryReadToAny(out ReadOnlySequence<T> sequence, ReadOnlySpan<T> delimiters, bool advancePastDelimiter = true) { return TryReadToAnyInternal(out sequence, delimiters, advancePastDelimiter); } private bool TryReadToAnyInternal(out ReadOnlySequence<T> sequence, ReadOnlySpan<T> delimiters, bool advancePastDelimiter, int skip = 0) { SequenceReader<T> copy = this; if (skip > 0) Advance(skip); ReadOnlySpan<T> remaining = UnreadSpan; while (!End) { int index = delimiters.Length == 2 ? remaining.IndexOfAny(delimiters[0], delimiters[1]) : remaining.IndexOfAny(delimiters); if (index != -1) { // Found one of the delimiters. Move to it, slice, then move past it. if (index > 0) { AdvanceCurrentSpan(index); } sequence = Sequence.Slice(copy.Position, Position); if (advancePastDelimiter) { Advance(1); } return true; } Advance(remaining.Length); remaining = CurrentSpan; } // Didn't find anything, reset our original state. this = copy; sequence = default; return false; } /// <summary> /// Try to read data until the entire given <paramref name="delimiter"/> matches. /// </summary> /// <param name="sequence">The read data, if any.</param> /// <param name="delimiter">The multi (T) delimiter.</param> /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param> /// <returns>True if the <paramref name="delimiter"/> was found.</returns> public bool TryReadTo(out ReadOnlySequence<T> sequence, ReadOnlySpan<T> delimiter, bool advancePastDelimiter = true) { if (delimiter.Length == 0) { sequence = default; return true; } SequenceReader<T> copy = this; bool advanced = false; while (!End) { if (!TryReadTo(out sequence, delimiter[0], advancePastDelimiter: false)) { this = copy; return false; } if (delimiter.Length == 1) { if (advancePastDelimiter) { Advance(1); } return true; } if (IsNext(delimiter)) { // Probably a faster way to do this, potentially by avoiding the Advance in the previous TryReadTo call if (advanced) { sequence = copy.Sequence.Slice(copy.Consumed, Consumed - copy.Consumed); } if (advancePastDelimiter) { Advance(delimiter.Length); } return true; } else { Advance(1); advanced = true; } } this = copy; sequence = default; return false; } /// <summary> /// Advance until the given <paramref name="delimiter"/>, if found. /// </summary> /// <param name="delimiter">The delimiter to search for.</param> /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param> /// <returns>True if the given <paramref name="delimiter"/> was found.</returns> public bool TryAdvanceTo(T delimiter, bool advancePastDelimiter = true) { ReadOnlySpan<T> remaining = UnreadSpan; int index = remaining.IndexOf(delimiter); if (index != -1) { Advance(advancePastDelimiter ? index + 1 : index); return true; } return TryReadToInternal(out _, delimiter, advancePastDelimiter); } /// <summary> /// Advance until any of the given <paramref name="delimiters"/>, if found. /// </summary> /// <param name="delimiters">The delimiters to search for.</param> /// <param name="advancePastDelimiter">True to move past the first found instance of any of the given <paramref name="delimiters"/>.</param> /// <returns>True if any of the given <paramref name="delimiters"/> were found.</returns> public bool TryAdvanceToAny(ReadOnlySpan<T> delimiters, bool advancePastDelimiter = true) { ReadOnlySpan<T> remaining = UnreadSpan; int index = remaining.IndexOfAny(delimiters); if (index != -1) { AdvanceCurrentSpan(index + (advancePastDelimiter ? 1 : 0)); return true; } return TryReadToAnyInternal(out _, delimiters, advancePastDelimiter); } /// <summary> /// Advance past consecutive instances of the given <paramref name="value"/>. /// </summary> /// <returns>How many positions the reader has been advanced.</returns> public long AdvancePast(T value) { long start = Consumed; do { // Advance past all matches in the current span int i; for (i = CurrentSpanIndex; i < CurrentSpan.Length && CurrentSpan[i].Equals(value); i++) { } int advanced = i - CurrentSpanIndex; if (advanced == 0) { // Didn't advance at all in this span, exit. break; } AdvanceCurrentSpan(advanced); // If we're at postion 0 after advancing and not at the End, // we're in a new span and should continue the loop. } while (CurrentSpanIndex == 0 && !End); return Consumed - start; } /// <summary> /// Skip consecutive instances of any of the given <paramref name="values"/>. /// </summary> /// <returns>How many positions the reader has been advanced.</returns> public long AdvancePastAny(ReadOnlySpan<T> values) { long start = Consumed; do { // Advance past all matches in the current span int i; for (i = CurrentSpanIndex; i < CurrentSpan.Length && values.IndexOf(CurrentSpan[i]) != -1; i++) { } int advanced = i - CurrentSpanIndex; if (advanced == 0) { // Didn't advance at all in this span, exit. break; } AdvanceCurrentSpan(advanced); // If we're at postion 0 after advancing and not at the End, // we're in a new span and should continue the loop. } while (CurrentSpanIndex == 0 && !End); return Consumed - start; } /// <summary> /// Advance past consecutive instances of any of the given values. /// </summary> /// <returns>How many positions the reader has been advanced.</returns> public long AdvancePastAny(T value0, T value1, T value2, T value3) { long start = Consumed; do { // Advance past all matches in the current span int i; for (i = CurrentSpanIndex; i < CurrentSpan.Length; i++) { T value = CurrentSpan[i]; if (!value.Equals(value0) && !value.Equals(value1) && !value.Equals(value2) && !value.Equals(value3)) { break; } } int advanced = i - CurrentSpanIndex; if (advanced == 0) { // Didn't advance at all in this span, exit. break; } AdvanceCurrentSpan(advanced); // If we're at postion 0 after advancing and not at the End, // we're in a new span and should continue the loop. } while (CurrentSpanIndex == 0 && !End); return Consumed - start; } /// <summary> /// Advance past consecutive instances of any of the given values. /// </summary> /// <returns>How many positions the reader has been advanced.</returns> public long AdvancePastAny(T value0, T value1, T value2) { long start = Consumed; do { // Advance past all matches in the current span int i; for (i = CurrentSpanIndex; i < CurrentSpan.Length; i++) { T value = CurrentSpan[i]; if (!value.Equals(value0) && !value.Equals(value1) && !value.Equals(value2)) { break; } } int advanced = i - CurrentSpanIndex; if (advanced == 0) { // Didn't advance at all in this span, exit. break; } AdvanceCurrentSpan(advanced); // If we're at postion 0 after advancing and not at the End, // we're in a new span and should continue the loop. } while (CurrentSpanIndex == 0 && !End); return Consumed - start; } /// <summary> /// Advance past consecutive instances of any of the given values. /// </summary> /// <returns>How many positions the reader has been advanced.</returns> public long AdvancePastAny(T value0, T value1) { long start = Consumed; do { // Advance past all matches in the current span int i; for (i = CurrentSpanIndex; i < CurrentSpan.Length; i++) { T value = CurrentSpan[i]; if (!value.Equals(value0) && !value.Equals(value1)) { break; } } int advanced = i - CurrentSpanIndex; if (advanced == 0) { // Didn't advance at all in this span, exit. break; } AdvanceCurrentSpan(advanced); // If we're at postion 0 after advancing and not at the End, // we're in a new span and should continue the loop. } while (CurrentSpanIndex == 0 && !End); return Consumed - start; } /// <summary> /// Check to see if the given <paramref name="next"/> value is next. /// </summary> /// <param name="next">The value to compare the next items to.</param> /// <param name="advancePast">Move past the <paramref name="next"/> value if found.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNext(T next, bool advancePast = false) { if (End) return false; if (CurrentSpan[CurrentSpanIndex].Equals(next)) { if (advancePast) { AdvanceCurrentSpan(1); } return true; } return false; } /// <summary> /// Check to see if the given <paramref name="next"/> values are next. /// </summary> /// <param name="next">The span to compare the next items to.</param> /// <param name="advancePast">Move past the <paramref name="next"/> values if found.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNext(ReadOnlySpan<T> next, bool advancePast = false) { ReadOnlySpan<T> unread = UnreadSpan; if (unread.StartsWith(next)) { if (advancePast) { AdvanceCurrentSpan(next.Length); } return true; } // Only check the slow path if there wasn't enough to satisfy next return unread.Length < next.Length && IsNextSlow(next, advancePast); } private unsafe bool IsNextSlow(ReadOnlySpan<T> next, bool advancePast) { ReadOnlySpan<T> currentSpan = UnreadSpan; // We should only come in here if we need more data than we have in our current span Debug.Assert(currentSpan.Length < next.Length); int fullLength = next.Length; SequencePosition nextPosition = _nextPosition; while (next.StartsWith(currentSpan)) { if (next.Length == currentSpan.Length) { // Fully matched if (advancePast) { Advance(fullLength); } return true; } // Need to check the next segment while (true) { if (!Sequence.TryGet(ref nextPosition, out ReadOnlyMemory<T> nextSegment, advance: true)) { // Nothing left return false; } if (nextSegment.Length > 0) { next = next.Slice(currentSpan.Length); currentSpan = nextSegment.Span; if (currentSpan.Length > next.Length) { currentSpan = currentSpan.Slice(0, next.Length); } break; } } } return false; } } }