Implement SequenceReader.TryPeek(long offset, out T value) (dotnet/corefx#42364)
authorMarco Rossignoli <marco.rossignoli@gmail.com>
Thu, 7 Nov 2019 18:31:48 +0000 (19:31 +0100)
committerJeremy Kuhne <jkuhne@microsoft.com>
Thu, 7 Nov 2019 18:31:48 +0000 (10:31 -0800)
* implement trypeek offset

* updates

* address PR feedback

* amend comments

Commit migrated from https://github.com/dotnet/corefx/commit/43d30a1184fda891d53062b42f7b4fd8f5303e06

src/libraries/System.Memory/ref/System.Memory.cs
src/libraries/System.Memory/src/System/Buffers/SequenceReader.cs
src/libraries/System.Memory/tests/SequenceReader/BasicTests.cs

index 58c0d2f..bd1cc07 100644 (file)
@@ -430,6 +430,7 @@ namespace System.Buffers
         public bool TryAdvanceToAny(System.ReadOnlySpan<T> delimiters, bool advancePastDelimiter = true) { throw null; }
         public readonly bool TryCopyTo(System.Span<T> destination) { throw null; }
         public readonly bool TryPeek(out T value) { throw null; }
+        public readonly bool TryPeek(long offset, out T value) { throw null; }
         public bool TryRead(out T value) { throw null; }
         public bool TryReadTo(out System.Buffers.ReadOnlySequence<T> sequence, System.ReadOnlySpan<T> delimiter, bool advancePastDelimiter = true) { throw null; }
         public bool TryReadTo(out System.Buffers.ReadOnlySequence<T> sequence, T delimiter, bool advancePastDelimiter = true) { throw null; }
index 3a1a296..18d75cd 100644 (file)
@@ -121,6 +121,58 @@ namespace System.Buffers
         }
 
         /// <summary>
+        /// Peeks at the next value at specific offset without advancing the reader.
+        /// </summary>
+        /// <param name="offset">The offset from current position.</param>
+        /// <param name="value">The next value or default if at the end.</param>
+        /// <returns>False if at the end of the reader.</returns>
+        public readonly bool TryPeek(long offset, out T value)
+        {
+            if (offset < 0)
+                ThrowHelper.ThrowArgumentOutOfRangeException_OffsetOutOfRange();
+
+            // If we've got data and offset is not out of bounds
+            if (!_moreData || Remaining <= offset)
+            {
+                value = default;
+                return false;
+            }
+
+            // If offset doesn't fall inside current segment move to next until we find correct one
+            if ((CurrentSpanIndex + offset) <= CurrentSpan.Length - 1)
+            {
+                value = CurrentSpan[CurrentSpanIndex + (int)offset];
+                return true;
+            }
+            else
+            {
+                long remainingOffset = offset - (CurrentSpan.Length - CurrentSpanIndex);
+                SequencePosition nextPosition = _nextPosition;
+                ReadOnlyMemory<T> currentMemory = default;
+
+                while (Sequence.TryGet(ref nextPosition, out currentMemory, true))
+                {
+                    // Skip empty segment
+                    if (currentMemory.Length > 0)
+                    {
+                        if (remainingOffset > currentMemory.Length - 1)
+                        {
+                            // Subtract current non consumed data
+                            remainingOffset -= currentMemory.Length;
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                }
+
+                value = currentMemory.Span[(int)remainingOffset];
+                return true;
+            }
+        }
+
+        /// <summary>
         /// Read the next value and advance the reader.
         /// </summary>
         /// <param name="value">The next value or default if at the end.</param>
index 3986dc6..e5c0625 100644 (file)
@@ -189,6 +189,139 @@ namespace System.Memory.Tests.SequenceReader
         }
 
         [Fact]
+        public void TryPeekOffset()
+        {
+            SequenceReader<T> reader = new SequenceReader<T>(Factory.CreateWithContent(GetInputData(10)));
+            Assert.True(reader.TryRead(out T first));
+            Assert.Equal(InputData[0], first);
+            Assert.True(reader.TryRead(out T second));
+            Assert.Equal(InputData[1], second);
+
+            Assert.True(reader.TryPeek(7, out T value));
+            Assert.Equal(InputData[9], value);
+
+            Assert.False(reader.TryPeek(8, out T defaultValue));
+            Assert.Equal(default, defaultValue);
+
+            Assert.Equal(2, reader.Consumed);
+            Assert.Equal(8, reader.Remaining);
+        }
+
+        [Fact]
+        public void TryPeekOffset_AfterEnd()
+        {
+            SequenceReader<T> reader = new SequenceReader<T>(Factory.CreateWithContent(GetInputData(2)));
+            Assert.True(reader.TryRead(out T first));
+            Assert.Equal(InputData[0], first);
+
+            Assert.True(reader.TryPeek(0, out T value));
+            Assert.Equal(InputData[1], value);
+            Assert.Equal(1, reader.Remaining);
+
+            Assert.False(reader.TryPeek(1, out T defaultValue));
+            Assert.Equal(default, defaultValue);
+        }
+
+        [Fact]
+        public void TryPeekOffset_RemainsZeroOffsetZero()
+        {
+            SequenceReader<T> reader = new SequenceReader<T>(Factory.CreateWithContent(GetInputData(1)));
+            Assert.True(reader.TryRead(out T first));
+            Assert.Equal(InputData[0], first);
+            Assert.Equal(0, reader.Remaining);
+            Assert.False(reader.TryPeek(0, out T defaultValue));
+            Assert.Equal(default, defaultValue);
+        }
+
+        [Fact]
+        public void TryPeekOffset_Empty()
+        {
+            SequenceReader<T> reader = new SequenceReader<T>(Factory.CreateWithContent(GetInputData(0)));
+            Assert.False(reader.TryPeek(0, out T defaultValue));
+            Assert.Equal(default, defaultValue);
+        }
+
+
+        [Fact]
+        public void TryPeekOffset_MultiSegment_StarAhead()
+        {
+            ReadOnlySpan<T> data = (T[])_inputData.Clone();
+
+            SequenceSegment<T> last = new SequenceSegment<T>();
+            last.SetMemory(new OwnedArray<T>(data.Slice(5).ToArray()), 0, 5);
+
+            SequenceSegment<T> first = new SequenceSegment<T>();
+            first.SetMemory(new OwnedArray<T>(data.Slice(0, 5).ToArray()), 0, 5);
+            first.SetNext(last);
+
+            ReadOnlySequence<T> sequence = new ReadOnlySequence<T>(first, first.Start, last, last.End);
+            SequenceReader<T> reader = new SequenceReader<T>(sequence);
+
+            // Move by 2 element
+            for (int i = 0; i < 2; i++)
+            {
+                Assert.True(reader.TryRead(out T val));
+                Assert.Equal(InputData[i], val);
+            }
+
+            // We're on element 3 we peek last element of first segment
+            Assert.True(reader.TryPeek(2, out T lastElementFirstSegment));
+            Assert.Equal(InputData[4], lastElementFirstSegment);
+
+            // We're on element 3 we peek first element of first segment
+            Assert.True(reader.TryPeek(3, out T fistElementSecondSegment));
+            Assert.Equal(InputData[5], fistElementSecondSegment);
+
+            // We're on element 3 we peek last element of second segment
+            Assert.True(reader.TryPeek(7, out T lastElementSecondSegment));
+            Assert.Equal(InputData[9], lastElementSecondSegment);
+
+            // 3 + 8 out of bounds
+            Assert.False(reader.TryPeek(8, out T defaultValue));
+            Assert.Equal(default, defaultValue);
+
+            Assert.Equal(2, reader.Consumed);
+            Assert.Equal(8, reader.Remaining);
+        }
+
+        [Fact]
+        public void TryPeekOffset_MultiSegment_GetFirstGetLast()
+        {
+            ReadOnlySpan<T> data = (T[])_inputData.Clone();
+
+            SequenceSegment<T> last = new SequenceSegment<T>();
+            last.SetMemory(new OwnedArray<T>(data.Slice(5).ToArray()), 0, 5);
+
+            SequenceSegment<T> first = new SequenceSegment<T>();
+            first.SetMemory(new OwnedArray<T>(data.Slice(0, 5).ToArray()), 0, 5);
+            first.SetNext(last);
+
+            ReadOnlySequence<T> sequence = new ReadOnlySequence<T>(first, first.Start, last, last.End);
+            SequenceReader<T> reader = new SequenceReader<T>(sequence);
+
+            Assert.True(reader.TryPeek(0, out T firstElement));
+            Assert.Equal(InputData[0], firstElement);
+
+            Assert.True(reader.TryPeek(data.Length - 1, out T lastElemen));
+            Assert.Equal(InputData[data.Length - 1], lastElemen);
+
+            Assert.Equal(0, reader.Consumed);
+            Assert.Equal(10, reader.Remaining);
+        }
+
+        [Fact]
+        public void TryPeekOffset_InvalidOffset()
+        {
+            ArgumentOutOfRangeException exception = Assert.Throws<ArgumentOutOfRangeException>(() =>
+            {
+                SequenceReader<T> reader = new SequenceReader<T>(Factory.CreateWithContent(GetInputData(10)));
+                reader.TryPeek(-1, out _);
+            });
+
+            Assert.Equal("offset", exception.ParamName);
+        }
+
+        [Fact]
         public void CursorIsCorrectAtEnd()
         {
             SequenceReader<T> reader = new SequenceReader<T>(Factory.CreateWithContent(GetInputData(2)));