Update NETStandard2.1 APIs (#17080)
[mono-project.git] / mcs / class / corlib / corefx / SequenceReader.Search.cs
blobaaa3d1d51fd6cbed72140d283c172d0dc39107ad
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
10 #if __MonoCS__
11 public ref partial struct SequenceReader<T> where T : IEquatable<T>
12 #else
13 public ref partial struct SequenceReader<T> where T : unmanaged, IEquatable<T>
14 #endif
16 /// <summary>
17 /// Try to read everything up to the given <paramref name="delimiter"/>.
18 /// </summary>
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);
28 if (index != -1)
30 span = index == 0 ? default : remaining.Slice(0, index);
31 AdvanceCurrentSpan(index + (advancePastDelimiter ? 1 : 0));
32 return true;
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))
42 span = default;
43 return false;
46 span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray();
47 return true;
50 /// <summary>
51 /// Try to read everything up to the given <paramref name="delimiter"/>, ignoring delimiters that are
52 /// preceded by <paramref name="delimiterEscape"/>.
53 /// </summary>
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));
68 return true;
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))
79 span = default;
80 return false;
83 Debug.Assert(sequence.Length > 0);
84 span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray();
85 return true;
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;
97 if (index >= 0)
99 if (index == 0 && priorEscape)
101 // We were in the escaped state, so skip this delimiter
102 priorEscape = false;
103 Advance(index + 1);
104 remaining = UnreadSpan;
105 goto Continue;
107 else if (index > 0 && remaining[index - 1].Equals(delimiterEscape))
109 // This delimiter might be skipped
111 // Count our escapes
112 int escapeCount = 1;
113 int i = index - 2;
114 for (; i >= 0; i--)
116 if (!remaining[i].Equals(delimiterEscape))
117 break;
119 if (i < 0 && priorEscape)
121 // Started and ended with escape, increment once more
122 escapeCount++;
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.
130 Advance(index + 1);
131 priorEscape = false;
132 remaining = UnreadSpan;
133 goto Continue;
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)
143 Advance(1);
145 return true;
147 else
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))
152 int escapeCount = 1;
153 int i = remaining.Length - 2;
154 for (; i >= 0; i--)
156 if (!remaining[i].Equals(delimiterEscape))
157 break;
160 escapeCount += remaining.Length - 2 - i;
161 if (i < 0 && priorEscape)
162 priorEscape = (escapeCount & 1) == 0; // equivalent to incrementing escapeCount before setting priorEscape
163 else
164 priorEscape = (escapeCount & 1) != 0;
166 else
168 priorEscape = false;
172 // Nothing in the current span, move to the end, checking for the skip delimiter
173 AdvanceCurrentSpan(remaining.Length);
174 remaining = CurrentSpan;
176 Continue:
177 index = remaining.IndexOf(delimiter);
178 } while (!End);
180 // Didn't find anything, reset our original state.
181 this = copy;
182 sequence = default;
183 return false;
186 /// <summary>
187 /// Try to read everything up to the given <paramref name="delimiter"/>.
188 /// </summary>
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;
202 if (skip > 0)
203 Advance(skip);
204 ReadOnlySpan<T> remaining = UnreadSpan;
206 while (_moreData)
208 int index = remaining.IndexOf(delimiter);
209 if (index != -1)
211 // Found the delimiter. Move to it, slice, then move past it.
212 if (index > 0)
214 AdvanceCurrentSpan(index);
217 sequence = Sequence.Slice(copy.Position, Position);
218 if (advancePastDelimiter)
220 Advance(1);
222 return true;
225 AdvanceCurrentSpan(remaining.Length);
226 remaining = CurrentSpan;
229 // Didn't find anything, reset our original state.
230 this = copy;
231 sequence = default;
232 return false;
235 /// <summary>
236 /// Try to read everything up to the given <paramref name="delimiter"/>, ignoring delimiters that are
237 /// preceded by <paramref name="delimiterEscape"/>.
238 /// </summary>
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;
251 while (_moreData)
253 int index = remaining.IndexOf(delimiter);
254 if (index != -1)
256 if (index == 0 && priorEscape)
258 // We were in the escaped state, so skip this delimiter
259 priorEscape = false;
260 Advance(index + 1);
261 remaining = UnreadSpan;
262 continue;
264 else if (index > 0 && remaining[index - 1].Equals(delimiterEscape))
266 // This delimiter might be skipped
268 // Count our escapes
269 int escapeCount = 0;
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
275 escapeCount++;
278 priorEscape = false;
279 if ((escapeCount & 1) != 0)
281 // Odd escape count means we're in the escaped state, so skip this delimiter
282 Advance(index + 1);
283 remaining = UnreadSpan;
284 continue;
288 // Found the delimiter. Move to it, slice, then move past it.
289 if (index > 0)
291 Advance(index);
294 sequence = Sequence.Slice(copy.Position, Position);
295 if (advancePastDelimiter)
297 Advance(1);
299 return true;
302 // No delimiter, need to check the end of the span for odd number of escapes then advance
304 int escapeCount = 0;
305 for (int i = remaining.Length; i > 0 && remaining[i - 1].Equals(delimiterEscape); i--, escapeCount++)
307 if (priorEscape && escapeCount == remaining.Length)
309 escapeCount++;
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.
320 this = copy;
321 sequence = default;
322 return false;
325 /// <summary>
326 /// Try to read everything up to the given <paramref name="delimiters"/>.
327 /// </summary>
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);
340 if (index != -1)
342 span = remaining.Slice(0, index);
343 Advance(index + (advancePastDelimiter ? 1 : 0));
344 return true;
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))
354 span = default;
355 return false;
358 span = sequence.IsSingleSegment ? sequence.First.Span : sequence.ToArray();
359 return true;
362 /// <summary>
363 /// Try to read everything up to the given <paramref name="delimiters"/>.
364 /// </summary>
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;
377 if (skip > 0)
378 Advance(skip);
379 ReadOnlySpan<T> remaining = UnreadSpan;
381 while (!End)
383 int index = delimiters.Length == 2
384 ? remaining.IndexOfAny(delimiters[0], delimiters[1])
385 : remaining.IndexOfAny(delimiters);
387 if (index != -1)
389 // Found one of the delimiters. Move to it, slice, then move past it.
390 if (index > 0)
392 AdvanceCurrentSpan(index);
395 sequence = Sequence.Slice(copy.Position, Position);
396 if (advancePastDelimiter)
398 Advance(1);
400 return true;
403 Advance(remaining.Length);
404 remaining = CurrentSpan;
407 // Didn't find anything, reset our original state.
408 this = copy;
409 sequence = default;
410 return false;
413 /// <summary>
414 /// Try to read data until the entire given <paramref name="delimiter"/> matches.
415 /// </summary>
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)
424 sequence = default;
425 return true;
428 SequenceReader<T> copy = this;
430 bool advanced = false;
431 while (!End)
433 if (!TryReadTo(out sequence, delimiter[0], advancePastDelimiter: false))
435 this = copy;
436 return false;
439 if (delimiter.Length == 1)
441 if (advancePastDelimiter)
443 Advance(1);
445 return true;
448 if (IsNext(delimiter))
450 // Probably a faster way to do this, potentially by avoiding the Advance in the previous TryReadTo call
451 if (advanced)
453 sequence = copy.Sequence.Slice(copy.Consumed, Consumed - copy.Consumed);
456 if (advancePastDelimiter)
458 Advance(delimiter.Length);
460 return true;
462 else
464 Advance(1);
465 advanced = true;
469 this = copy;
470 sequence = default;
471 return false;
474 /// <summary>
475 /// Advance until the given <paramref name="delimiter"/>, if found.
476 /// </summary>
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);
484 if (index != -1)
486 Advance(advancePastDelimiter ? index + 1 : index);
487 return true;
490 return TryReadToInternal(out _, delimiter, advancePastDelimiter);
493 /// <summary>
494 /// Advance until any of the given <paramref name="delimiters"/>, if found.
495 /// </summary>
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);
503 if (index != -1)
505 AdvanceCurrentSpan(index + (advancePastDelimiter ? 1 : 0));
506 return true;
509 return TryReadToAnyInternal(out _, delimiters, advancePastDelimiter);
512 /// <summary>
513 /// Advance past consecutive instances of the given <paramref name="value"/>.
514 /// </summary>
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
523 int i;
524 for (i = CurrentSpanIndex; i < CurrentSpan.Length && CurrentSpan[i].Equals(value); i++)
528 int advanced = i - CurrentSpanIndex;
529 if (advanced == 0)
531 // Didn't advance at all in this span, exit.
532 break;
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;
544 /// <summary>
545 /// Skip consecutive instances of any of the given <paramref name="values"/>.
546 /// </summary>
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
555 int i;
556 for (i = CurrentSpanIndex; i < CurrentSpan.Length && values.IndexOf(CurrentSpan[i]) != -1; i++)
560 int advanced = i - CurrentSpanIndex;
561 if (advanced == 0)
563 // Didn't advance at all in this span, exit.
564 break;
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;
576 /// <summary>
577 /// Advance past consecutive instances of any of the given values.
578 /// </summary>
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
587 int i;
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))
593 break;
597 int advanced = i - CurrentSpanIndex;
598 if (advanced == 0)
600 // Didn't advance at all in this span, exit.
601 break;
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;
613 /// <summary>
614 /// Advance past consecutive instances of any of the given values.
615 /// </summary>
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
624 int i;
625 for (i = CurrentSpanIndex; i < CurrentSpan.Length; i++)
627 T value = CurrentSpan[i];
628 if (!value.Equals(value0) && !value.Equals(value1) && !value.Equals(value2))
630 break;
634 int advanced = i - CurrentSpanIndex;
635 if (advanced == 0)
637 // Didn't advance at all in this span, exit.
638 break;
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;
650 /// <summary>
651 /// Advance past consecutive instances of any of the given values.
652 /// </summary>
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
661 int i;
662 for (i = CurrentSpanIndex; i < CurrentSpan.Length; i++)
664 T value = CurrentSpan[i];
665 if (!value.Equals(value0) && !value.Equals(value1))
667 break;
671 int advanced = i - CurrentSpanIndex;
672 if (advanced == 0)
674 // Didn't advance at all in this span, exit.
675 break;
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;
687 /// <summary>
688 /// Check to see if the given <paramref name="next"/> value is next.
689 /// </summary>
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)
695 if (End)
696 return false;
698 if (CurrentSpan[CurrentSpanIndex].Equals(next))
700 if (advancePast)
702 AdvanceCurrentSpan(1);
704 return true;
706 return false;
709 /// <summary>
710 /// Check to see if the given <paramref name="next"/> values are next.
711 /// </summary>
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))
720 if (advancePast)
722 AdvanceCurrentSpan(next.Length);
724 return true;
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)
745 // Fully matched
746 if (advancePast)
748 Advance(fullLength);
750 return true;
753 // Need to check the next segment
754 while (true)
756 if (!Sequence.TryGet(ref nextPosition, out ReadOnlyMemory<T> nextSegment, advance: true))
758 // Nothing left
759 return false;
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);
770 break;
775 return false;