1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 using System
.Diagnostics
;
6 using System
.Runtime
.CompilerServices
;
8 namespace System
.Buffers
11 public ref partial struct SequenceReader
<T
> where T
: IEquatable
<T
>
13 public ref partial struct SequenceReader
<T
> where T
: unmanaged
, IEquatable
<T
>
17 /// Try to read everything up to the given <paramref name="delimiter"/>.
19 /// <param name="span">The read data, if any.</param>
20 /// <param name="delimiter">The delimiter to look for.</param>
21 /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param>
22 /// <returns>True if the <paramref name="delimiter"/> was found.</returns>
23 public bool TryReadTo(out ReadOnlySpan
<T
> span
, T delimiter
, bool advancePastDelimiter
= true)
25 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
26 int index
= remaining
.IndexOf(delimiter
);
30 span
= index
== 0 ? default : remaining
.Slice(0, index
);
31 AdvanceCurrentSpan(index
+ (advancePastDelimiter
? 1 : 0));
35 return TryReadToSlow(out span
, delimiter
, advancePastDelimiter
);
38 private bool TryReadToSlow(out ReadOnlySpan
<T
> span
, T delimiter
, bool advancePastDelimiter
)
40 if (!TryReadToInternal(out ReadOnlySequence
<T
> sequence
, delimiter
, advancePastDelimiter
, CurrentSpan
.Length
- CurrentSpanIndex
))
46 span
= sequence
.IsSingleSegment
? sequence
.First
.Span
: sequence
.ToArray();
51 /// Try to read everything up to the given <paramref name="delimiter"/>, ignoring delimiters that are
52 /// preceded by <paramref name="delimiterEscape"/>.
54 /// <param name="span">The read data, if any.</param>
55 /// <param name="delimiter">The delimiter to look for.</param>
56 /// <param name="delimiterEscape">If found prior to <paramref name="delimiter"/> it will skip that occurrence.</param>
57 /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param>
58 /// <returns>True if the <paramref name="delimiter"/> was found.</returns>
59 public bool TryReadTo(out ReadOnlySpan
<T
> span
, T delimiter
, T delimiterEscape
, bool advancePastDelimiter
= true)
61 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
62 int index
= remaining
.IndexOf(delimiter
);
64 if ((index
> 0 && !remaining
[index
- 1].Equals(delimiterEscape
)) || index
== 0)
66 span
= remaining
.Slice(0, index
);
67 AdvanceCurrentSpan(index
+ (advancePastDelimiter
? 1 : 0));
71 // This delimiter might be skipped, go down the slow path
72 return TryReadToSlow(out span
, delimiter
, delimiterEscape
, index
, advancePastDelimiter
);
75 private bool TryReadToSlow(out ReadOnlySpan
<T
> span
, T delimiter
, T delimiterEscape
, int index
, bool advancePastDelimiter
)
77 if (!TryReadToSlow(out ReadOnlySequence
<T
> sequence
, delimiter
, delimiterEscape
, index
, advancePastDelimiter
))
83 Debug
.Assert(sequence
.Length
> 0);
84 span
= sequence
.IsSingleSegment
? sequence
.First
.Span
: sequence
.ToArray();
88 private bool TryReadToSlow(out ReadOnlySequence
<T
> sequence
, T delimiter
, T delimiterEscape
, int index
, bool advancePastDelimiter
)
90 SequenceReader
<T
> copy
= this;
92 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
93 bool priorEscape
= false;
99 if (index
== 0 && priorEscape
)
101 // We were in the escaped state, so skip this delimiter
104 remaining
= UnreadSpan
;
107 else if (index
> 0 && remaining
[index
- 1].Equals(delimiterEscape
))
109 // This delimiter might be skipped
116 if (!remaining
[i
].Equals(delimiterEscape
))
119 if (i
< 0 && priorEscape
)
121 // Started and ended with escape, increment once more
124 escapeCount
+= index
- 2 - i
;
126 if ((escapeCount
& 1) != 0)
128 // An odd escape count means we're currently escaped,
129 // skip the delimiter and reset escaped state.
132 remaining
= UnreadSpan
;
137 // Found the delimiter. Move to it, slice, then move past it.
138 AdvanceCurrentSpan(index
);
140 sequence
= Sequence
.Slice(copy
.Position
, Position
);
141 if (advancePastDelimiter
)
149 // No delimiter, need to check the end of the span for odd number of escapes then advance
150 if (remaining
.Length
> 0 && remaining
[remaining
.Length
- 1].Equals(delimiterEscape
))
153 int i
= remaining
.Length
- 2;
156 if (!remaining
[i
].Equals(delimiterEscape
))
160 escapeCount
+= remaining
.Length
- 2 - i
;
161 if (i
< 0 && priorEscape
)
162 priorEscape
= (escapeCount
& 1) == 0; // equivalent to incrementing escapeCount before setting priorEscape
164 priorEscape
= (escapeCount
& 1) != 0;
172 // Nothing in the current span, move to the end, checking for the skip delimiter
173 AdvanceCurrentSpan(remaining
.Length
);
174 remaining
= CurrentSpan
;
177 index
= remaining
.IndexOf(delimiter
);
180 // Didn't find anything, reset our original state.
187 /// Try to read everything up to the given <paramref name="delimiter"/>.
189 /// <param name="sequence">The read data, if any.</param>
190 /// <param name="delimiter">The delimiter to look for.</param>
191 /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param>
192 /// <returns>True if the <paramref name="delimiter"/> was found.</returns>
193 public bool TryReadTo(out ReadOnlySequence
<T
> sequence
, T delimiter
, bool advancePastDelimiter
= true)
195 return TryReadToInternal(out sequence
, delimiter
, advancePastDelimiter
);
198 private bool TryReadToInternal(out ReadOnlySequence
<T
> sequence
, T delimiter
, bool advancePastDelimiter
, int skip
= 0)
200 Debug
.Assert(skip
>= 0);
201 SequenceReader
<T
> copy
= this;
204 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
208 int index
= remaining
.IndexOf(delimiter
);
211 // Found the delimiter. Move to it, slice, then move past it.
214 AdvanceCurrentSpan(index
);
217 sequence
= Sequence
.Slice(copy
.Position
, Position
);
218 if (advancePastDelimiter
)
225 AdvanceCurrentSpan(remaining
.Length
);
226 remaining
= CurrentSpan
;
229 // Didn't find anything, reset our original state.
236 /// Try to read everything up to the given <paramref name="delimiter"/>, ignoring delimiters that are
237 /// preceded by <paramref name="delimiterEscape"/>.
239 /// <param name="sequence">The read data, if any.</param>
240 /// <param name="delimiter">The delimiter to look for.</param>
241 /// <param name="delimiterEscape">If found prior to <paramref name="delimiter"/> it will skip that occurrence.</param>
242 /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param>
243 /// <returns>True if the <paramref name="delimiter"/> was found.</returns>
244 public bool TryReadTo(out ReadOnlySequence
<T
> sequence
, T delimiter
, T delimiterEscape
, bool advancePastDelimiter
= true)
246 SequenceReader
<T
> copy
= this;
248 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
249 bool priorEscape
= false;
253 int index
= remaining
.IndexOf(delimiter
);
256 if (index
== 0 && priorEscape
)
258 // We were in the escaped state, so skip this delimiter
261 remaining
= UnreadSpan
;
264 else if (index
> 0 && remaining
[index
- 1].Equals(delimiterEscape
))
266 // This delimiter might be skipped
270 for (int i
= index
; i
> 0 && remaining
[i
- 1].Equals(delimiterEscape
); i
--, escapeCount
++)
272 if (escapeCount
== index
&& priorEscape
)
274 // Started and ended with escape, increment once more
279 if ((escapeCount
& 1) != 0)
281 // Odd escape count means we're in the escaped state, so skip this delimiter
283 remaining
= UnreadSpan
;
288 // Found the delimiter. Move to it, slice, then move past it.
294 sequence
= Sequence
.Slice(copy
.Position
, Position
);
295 if (advancePastDelimiter
)
302 // No delimiter, need to check the end of the span for odd number of escapes then advance
305 for (int i
= remaining
.Length
; i
> 0 && remaining
[i
- 1].Equals(delimiterEscape
); i
--, escapeCount
++)
307 if (priorEscape
&& escapeCount
== remaining
.Length
)
311 priorEscape
= escapeCount
% 2 != 0;
314 // Nothing in the current span, move to the end, checking for the skip delimiter
315 Advance(remaining
.Length
);
316 remaining
= CurrentSpan
;
319 // Didn't find anything, reset our original state.
326 /// Try to read everything up to the given <paramref name="delimiters"/>.
328 /// <param name="span">The read data, if any.</param>
329 /// <param name="delimiters">The delimiters to look for.</param>
330 /// <param name="advancePastDelimiter">True to move past the first found instance of any of the given <paramref name="delimiters"/>.</param>
331 /// <returns>True if any of the <paramref name="delimiters"/> were found.</returns>
332 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
333 public bool TryReadToAny(out ReadOnlySpan
<T
> span
, ReadOnlySpan
<T
> delimiters
, bool advancePastDelimiter
= true)
335 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
336 int index
= delimiters
.Length
== 2
337 ? remaining
.IndexOfAny(delimiters
[0], delimiters
[1])
338 : remaining
.IndexOfAny(delimiters
);
342 span
= remaining
.Slice(0, index
);
343 Advance(index
+ (advancePastDelimiter
? 1 : 0));
347 return TryReadToAnySlow(out span
, delimiters
, advancePastDelimiter
);
350 private bool TryReadToAnySlow(out ReadOnlySpan
<T
> span
, ReadOnlySpan
<T
> delimiters
, bool advancePastDelimiter
)
352 if (!TryReadToAnyInternal(out ReadOnlySequence
<T
> sequence
, delimiters
, advancePastDelimiter
, CurrentSpan
.Length
- CurrentSpanIndex
))
358 span
= sequence
.IsSingleSegment
? sequence
.First
.Span
: sequence
.ToArray();
363 /// Try to read everything up to the given <paramref name="delimiters"/>.
365 /// <param name="sequence">The read data, if any.</param>
366 /// <param name="delimiters">The delimiters to look for.</param>
367 /// <param name="advancePastDelimiter">True to move past the first found instance of any of the given <paramref name="delimiters"/>.</param>
368 /// <returns>True if any of the <paramref name="delimiters"/> were found.</returns>
369 public bool TryReadToAny(out ReadOnlySequence
<T
> sequence
, ReadOnlySpan
<T
> delimiters
, bool advancePastDelimiter
= true)
371 return TryReadToAnyInternal(out sequence
, delimiters
, advancePastDelimiter
);
374 private bool TryReadToAnyInternal(out ReadOnlySequence
<T
> sequence
, ReadOnlySpan
<T
> delimiters
, bool advancePastDelimiter
, int skip
= 0)
376 SequenceReader
<T
> copy
= this;
379 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
383 int index
= delimiters
.Length
== 2
384 ? remaining
.IndexOfAny(delimiters
[0], delimiters
[1])
385 : remaining
.IndexOfAny(delimiters
);
389 // Found one of the delimiters. Move to it, slice, then move past it.
392 AdvanceCurrentSpan(index
);
395 sequence
= Sequence
.Slice(copy
.Position
, Position
);
396 if (advancePastDelimiter
)
403 Advance(remaining
.Length
);
404 remaining
= CurrentSpan
;
407 // Didn't find anything, reset our original state.
414 /// Try to read data until the entire given <paramref name="delimiter"/> matches.
416 /// <param name="sequence">The read data, if any.</param>
417 /// <param name="delimiter">The multi (T) delimiter.</param>
418 /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param>
419 /// <returns>True if the <paramref name="delimiter"/> was found.</returns>
420 public bool TryReadTo(out ReadOnlySequence
<T
> sequence
, ReadOnlySpan
<T
> delimiter
, bool advancePastDelimiter
= true)
422 if (delimiter
.Length
== 0)
428 SequenceReader
<T
> copy
= this;
430 bool advanced
= false;
433 if (!TryReadTo(out sequence
, delimiter
[0], advancePastDelimiter
: false))
439 if (delimiter
.Length
== 1)
441 if (advancePastDelimiter
)
448 if (IsNext(delimiter
))
450 // Probably a faster way to do this, potentially by avoiding the Advance in the previous TryReadTo call
453 sequence
= copy
.Sequence
.Slice(copy
.Consumed
, Consumed
- copy
.Consumed
);
456 if (advancePastDelimiter
)
458 Advance(delimiter
.Length
);
475 /// Advance until the given <paramref name="delimiter"/>, if found.
477 /// <param name="delimiter">The delimiter to search for.</param>
478 /// <param name="advancePastDelimiter">True to move past the <paramref name="delimiter"/> if found.</param>
479 /// <returns>True if the given <paramref name="delimiter"/> was found.</returns>
480 public bool TryAdvanceTo(T delimiter
, bool advancePastDelimiter
= true)
482 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
483 int index
= remaining
.IndexOf(delimiter
);
486 Advance(advancePastDelimiter
? index
+ 1 : index
);
490 return TryReadToInternal(out _
, delimiter
, advancePastDelimiter
);
494 /// Advance until any of the given <paramref name="delimiters"/>, if found.
496 /// <param name="delimiters">The delimiters to search for.</param>
497 /// <param name="advancePastDelimiter">True to move past the first found instance of any of the given <paramref name="delimiters"/>.</param>
498 /// <returns>True if any of the given <paramref name="delimiters"/> were found.</returns>
499 public bool TryAdvanceToAny(ReadOnlySpan
<T
> delimiters
, bool advancePastDelimiter
= true)
501 ReadOnlySpan
<T
> remaining
= UnreadSpan
;
502 int index
= remaining
.IndexOfAny(delimiters
);
505 AdvanceCurrentSpan(index
+ (advancePastDelimiter
? 1 : 0));
509 return TryReadToAnyInternal(out _
, delimiters
, advancePastDelimiter
);
513 /// Advance past consecutive instances of the given <paramref name="value"/>.
515 /// <returns>How many positions the reader has been advanced.</returns>
516 public long AdvancePast(T
value)
518 long start
= Consumed
;
522 // Advance past all matches in the current span
524 for (i
= CurrentSpanIndex
; i
< CurrentSpan
.Length
&& CurrentSpan
[i
].Equals(value); i
++)
528 int advanced
= i
- CurrentSpanIndex
;
531 // Didn't advance at all in this span, exit.
535 AdvanceCurrentSpan(advanced
);
537 // If we're at postion 0 after advancing and not at the End,
538 // we're in a new span and should continue the loop.
539 } while (CurrentSpanIndex
== 0 && !End
);
541 return Consumed
- start
;
545 /// Skip consecutive instances of any of the given <paramref name="values"/>.
547 /// <returns>How many positions the reader has been advanced.</returns>
548 public long AdvancePastAny(ReadOnlySpan
<T
> values
)
550 long start
= Consumed
;
554 // Advance past all matches in the current span
556 for (i
= CurrentSpanIndex
; i
< CurrentSpan
.Length
&& values
.IndexOf(CurrentSpan
[i
]) != -1; i
++)
560 int advanced
= i
- CurrentSpanIndex
;
563 // Didn't advance at all in this span, exit.
567 AdvanceCurrentSpan(advanced
);
569 // If we're at postion 0 after advancing and not at the End,
570 // we're in a new span and should continue the loop.
571 } while (CurrentSpanIndex
== 0 && !End
);
573 return Consumed
- start
;
577 /// Advance past consecutive instances of any of the given values.
579 /// <returns>How many positions the reader has been advanced.</returns>
580 public long AdvancePastAny(T value0
, T value1
, T value2
, T value3
)
582 long start
= Consumed
;
586 // Advance past all matches in the current span
588 for (i
= CurrentSpanIndex
; i
< CurrentSpan
.Length
; i
++)
590 T
value = CurrentSpan
[i
];
591 if (!value.Equals(value0
) && !value.Equals(value1
) && !value.Equals(value2
) && !value.Equals(value3
))
597 int advanced
= i
- CurrentSpanIndex
;
600 // Didn't advance at all in this span, exit.
604 AdvanceCurrentSpan(advanced
);
606 // If we're at postion 0 after advancing and not at the End,
607 // we're in a new span and should continue the loop.
608 } while (CurrentSpanIndex
== 0 && !End
);
610 return Consumed
- start
;
614 /// Advance past consecutive instances of any of the given values.
616 /// <returns>How many positions the reader has been advanced.</returns>
617 public long AdvancePastAny(T value0
, T value1
, T value2
)
619 long start
= Consumed
;
623 // Advance past all matches in the current span
625 for (i
= CurrentSpanIndex
; i
< CurrentSpan
.Length
; i
++)
627 T
value = CurrentSpan
[i
];
628 if (!value.Equals(value0
) && !value.Equals(value1
) && !value.Equals(value2
))
634 int advanced
= i
- CurrentSpanIndex
;
637 // Didn't advance at all in this span, exit.
641 AdvanceCurrentSpan(advanced
);
643 // If we're at postion 0 after advancing and not at the End,
644 // we're in a new span and should continue the loop.
645 } while (CurrentSpanIndex
== 0 && !End
);
647 return Consumed
- start
;
651 /// Advance past consecutive instances of any of the given values.
653 /// <returns>How many positions the reader has been advanced.</returns>
654 public long AdvancePastAny(T value0
, T value1
)
656 long start
= Consumed
;
660 // Advance past all matches in the current span
662 for (i
= CurrentSpanIndex
; i
< CurrentSpan
.Length
; i
++)
664 T
value = CurrentSpan
[i
];
665 if (!value.Equals(value0
) && !value.Equals(value1
))
671 int advanced
= i
- CurrentSpanIndex
;
674 // Didn't advance at all in this span, exit.
678 AdvanceCurrentSpan(advanced
);
680 // If we're at postion 0 after advancing and not at the End,
681 // we're in a new span and should continue the loop.
682 } while (CurrentSpanIndex
== 0 && !End
);
684 return Consumed
- start
;
688 /// Check to see if the given <paramref name="next"/> value is next.
690 /// <param name="next">The value to compare the next items to.</param>
691 /// <param name="advancePast">Move past the <paramref name="next"/> value if found.</param>
692 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
693 public bool IsNext(T next
, bool advancePast
= false)
698 if (CurrentSpan
[CurrentSpanIndex
].Equals(next
))
702 AdvanceCurrentSpan(1);
710 /// Check to see if the given <paramref name="next"/> values are next.
712 /// <param name="next">The span to compare the next items to.</param>
713 /// <param name="advancePast">Move past the <paramref name="next"/> values if found.</param>
714 [MethodImpl(MethodImplOptions
.AggressiveInlining
)]
715 public bool IsNext(ReadOnlySpan
<T
> next
, bool advancePast
= false)
717 ReadOnlySpan
<T
> unread
= UnreadSpan
;
718 if (unread
.StartsWith(next
))
722 AdvanceCurrentSpan(next
.Length
);
727 // Only check the slow path if there wasn't enough to satisfy next
728 return unread
.Length
< next
.Length
&& IsNextSlow(next
, advancePast
);
731 private unsafe bool IsNextSlow(ReadOnlySpan
<T
> next
, bool advancePast
)
733 ReadOnlySpan
<T
> currentSpan
= UnreadSpan
;
735 // We should only come in here if we need more data than we have in our current span
736 Debug
.Assert(currentSpan
.Length
< next
.Length
);
738 int fullLength
= next
.Length
;
739 SequencePosition nextPosition
= _nextPosition
;
741 while (next
.StartsWith(currentSpan
))
743 if (next
.Length
== currentSpan
.Length
)
753 // Need to check the next segment
756 if (!Sequence
.TryGet(ref nextPosition
, out ReadOnlyMemory
<T
> nextSegment
, advance
: true))
762 if (nextSegment
.Length
> 0)
764 next
= next
.Slice(currentSpan
.Length
);
765 currentSpan
= nextSegment
.Span
;
766 if (currentSpan
.Length
> next
.Length
)
768 currentSpan
= currentSpan
.Slice(0, next
.Length
);