// 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 where T : IEquatable #else public ref partial struct SequenceReader where T : unmanaged, IEquatable #endif { /// /// Try to read everything up to the given . /// /// The read data, if any. /// The delimiter to look for. /// True to move past the if found. /// True if the was found. public bool TryReadTo(out ReadOnlySpan span, T delimiter, bool advancePastDelimiter = true) { ReadOnlySpan 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 span, T delimiter, bool advancePastDelimiter) { if (!TryReadToInternal(out ReadOnlySequence sequence, delimiter, advancePastDelimiter, CurrentSpan.Length - CurrentSpanIndex)) { span = default; return false; } span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray(); return true; } /// /// Try to read everything up to the given , ignoring delimiters that are /// preceded by . /// /// The read data, if any. /// The delimiter to look for. /// If found prior to it will skip that occurrence. /// True to move past the if found. /// True if the was found. public bool TryReadTo(out ReadOnlySpan span, T delimiter, T delimiterEscape, bool advancePastDelimiter = true) { ReadOnlySpan 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 span, T delimiter, T delimiterEscape, int index, bool advancePastDelimiter) { if (!TryReadToSlow(out ReadOnlySequence 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 sequence, T delimiter, T delimiterEscape, int index, bool advancePastDelimiter) { SequenceReader copy = this; ReadOnlySpan 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; } /// /// Try to read everything up to the given . /// /// The read data, if any. /// The delimiter to look for. /// True to move past the if found. /// True if the was found. public bool TryReadTo(out ReadOnlySequence sequence, T delimiter, bool advancePastDelimiter = true) { return TryReadToInternal(out sequence, delimiter, advancePastDelimiter); } private bool TryReadToInternal(out ReadOnlySequence sequence, T delimiter, bool advancePastDelimiter, int skip = 0) { Debug.Assert(skip >= 0); SequenceReader copy = this; if (skip > 0) Advance(skip); ReadOnlySpan 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; } /// /// Try to read everything up to the given , ignoring delimiters that are /// preceded by . /// /// The read data, if any. /// The delimiter to look for. /// If found prior to it will skip that occurrence. /// True to move past the if found. /// True if the was found. public bool TryReadTo(out ReadOnlySequence sequence, T delimiter, T delimiterEscape, bool advancePastDelimiter = true) { SequenceReader copy = this; ReadOnlySpan 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; } /// /// Try to read everything up to the given . /// /// The read data, if any. /// The delimiters to look for. /// True to move past the first found instance of any of the given . /// True if any of the were found. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryReadToAny(out ReadOnlySpan span, ReadOnlySpan delimiters, bool advancePastDelimiter = true) { ReadOnlySpan 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 span, ReadOnlySpan delimiters, bool advancePastDelimiter) { if (!TryReadToAnyInternal(out ReadOnlySequence sequence, delimiters, advancePastDelimiter, CurrentSpan.Length - CurrentSpanIndex)) { span = default; return false; } span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray(); return true; } /// /// Try to read everything up to the given . /// /// The read data, if any. /// The delimiters to look for. /// True to move past the first found instance of any of the given . /// True if any of the were found. public bool TryReadToAny(out ReadOnlySequence sequence, ReadOnlySpan delimiters, bool advancePastDelimiter = true) { return TryReadToAnyInternal(out sequence, delimiters, advancePastDelimiter); } private bool TryReadToAnyInternal(out ReadOnlySequence sequence, ReadOnlySpan delimiters, bool advancePastDelimiter, int skip = 0) { SequenceReader copy = this; if (skip > 0) Advance(skip); ReadOnlySpan 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; } /// /// Try to read data until the entire given matches. /// /// The read data, if any. /// The multi (T) delimiter. /// True to move past the if found. /// True if the was found. public bool TryReadTo(out ReadOnlySequence sequence, ReadOnlySpan delimiter, bool advancePastDelimiter = true) { if (delimiter.Length == 0) { sequence = default; return true; } SequenceReader 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; } /// /// Advance until the given , if found. /// /// The delimiter to search for. /// True to move past the if found. /// True if the given was found. public bool TryAdvanceTo(T delimiter, bool advancePastDelimiter = true) { ReadOnlySpan remaining = UnreadSpan; int index = remaining.IndexOf(delimiter); if (index != -1) { Advance(advancePastDelimiter ? index + 1 : index); return true; } return TryReadToInternal(out _, delimiter, advancePastDelimiter); } /// /// Advance until any of the given , if found. /// /// The delimiters to search for. /// True to move past the first found instance of any of the given . /// True if any of the given were found. public bool TryAdvanceToAny(ReadOnlySpan delimiters, bool advancePastDelimiter = true) { ReadOnlySpan remaining = UnreadSpan; int index = remaining.IndexOfAny(delimiters); if (index != -1) { AdvanceCurrentSpan(index + (advancePastDelimiter ? 1 : 0)); return true; } return TryReadToAnyInternal(out _, delimiters, advancePastDelimiter); } /// /// Advance past consecutive instances of the given . /// /// How many positions the reader has been advanced. 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; } /// /// Skip consecutive instances of any of the given . /// /// How many positions the reader has been advanced. public long AdvancePastAny(ReadOnlySpan 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; } /// /// Advance past consecutive instances of any of the given values. /// /// How many positions the reader has been advanced. 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; } /// /// Advance past consecutive instances of any of the given values. /// /// How many positions the reader has been advanced. 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; } /// /// Advance past consecutive instances of any of the given values. /// /// How many positions the reader has been advanced. 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; } /// /// Check to see if the given value is next. /// /// The value to compare the next items to. /// Move past the value if found. [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; } /// /// Check to see if the given values are next. /// /// The span to compare the next items to. /// Move past the values if found. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsNext(ReadOnlySpan next, bool advancePast = false) { ReadOnlySpan 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 next, bool advancePast) { ReadOnlySpan 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 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; } } }