| File: System\SpanReader.cs | Web Access |
| Project: src\winforms\src\System.Private.Windows.Core\src\Microsoft.Private.Windows.Core.csproj (Microsoft.Private.Windows.Core) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace System; /// <summary> /// Fast stack based <see cref="ReadOnlySpan{T}"/> reader. /// </summary> /// <remarks> /// <para> /// Care must be used when reading struct values that depend on a specific field state for members to work /// correctly. For example, <see cref="DateTime"/> has a very specific set of valid values for its packed /// <see langword="ulong"/> field. /// </para> /// <para> /// Inspired by SequenceReader{T} patterns. /// </para> /// </remarks> internal unsafe ref struct SpanReader<T>(ReadOnlySpan<T> span) where T : unmanaged, IEquatable<T> { private ReadOnlySpan<T> _unread = span; public ReadOnlySpan<T> Span { get; } = span; public int Position { readonly get => Span.Length - _unread.Length; set => _unread = Span[value..]; } /// <summary> /// Try to read everything up to the given <paramref name="delimiter"/>. Advances the reader past the /// <paramref name="delimiter"/> if found. /// </summary> /// <inheritdoc cref="TryReadTo(T, bool, out ReadOnlySpan{T})"/> public bool TryReadTo(T delimiter, out ReadOnlySpan<T> span) => TryReadTo(delimiter, advancePastDelimiter: true, out span); /// <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"><see langword="true"/> to move past the <paramref name="delimiter"/> if found.</param> /// <returns><see langword="true"/> if the <paramref name="delimiter"/> was found.</returns> public bool TryReadTo(T delimiter, bool advancePastDelimiter, out ReadOnlySpan<T> span) { bool found = false; int index = _unread.IndexOf(delimiter); span = default; if (index != -1) { found = true; if (index > 0) { span = _unread; UncheckedSliceTo(ref span, index); if (advancePastDelimiter) { index++; } UnsafeAdvance(index); } } return found; } /// <summary> /// Try to read the next value. /// </summary> public bool TryRead(out T value) { bool success; if (_unread.IsEmpty) { value = default; success = false; } else { success = true; value = _unread[0]; UnsafeAdvance(1); } return success; } /// <summary> /// Try to read a span of the given <paramref name="count"/>. /// </summary> public bool TryRead(int count, out ReadOnlySpan<T> span) { bool success; if (count > _unread.Length) { span = default; success = false; } else { success = true; span = _unread[..count]; UnsafeAdvance(count); } return success; } /// <summary> /// Try to read a value of the given type. The size of the value must be evenly divisible by the size of /// <typeparamref name="T"/>. /// </summary> /// <remarks> /// <para> /// This is just a straight copy of bits. If <typeparamref name="TValue"/> has methods that depend on /// specific field value constraints this could be unsafe. /// </para> /// <para> /// The compiler will often optimize away the struct copy if you only read from the value. /// </para> /// </remarks> public bool TryRead<TValue>(out TValue value) where TValue : unmanaged { if (sizeof(TValue) < sizeof(T) || sizeof(TValue) % sizeof(T) != 0) { throw new ArgumentException($"The size of {nameof(TValue)} must be evenly divisible by the size of {nameof(T)}."); } bool success; if (sizeof(TValue) > _unread.Length * sizeof(T)) { value = default; success = false; } else { success = true; value = Unsafe.ReadUnaligned<TValue>(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(_unread))); UnsafeAdvance(sizeof(TValue) / sizeof(T)); } return success; } /// <summary> /// Try to read a span of values of the given type. The size of the value must be evenly divisible by the size of /// <typeparamref name="T"/>. /// </summary> /// <remarks> /// <para> /// This effectively does a <see cref="MemoryMarshal.Cast{TFrom, TTo}(ReadOnlySpan{TFrom})"/> and the same /// caveats apply about safety. /// </para> /// </remarks> public bool TryRead<TValue>(int count, out ReadOnlySpan<TValue> value) where TValue : unmanaged { if (sizeof(TValue) < sizeof(T) || sizeof(TValue) % sizeof(T) != 0) { throw new ArgumentException($"The size of {nameof(TValue)} must be evenly divisible by the size of {nameof(T)}."); } bool success; if (sizeof(TValue) * count > _unread.Length * sizeof(T)) { value = default; success = false; } else { success = true; #if NETFRAMEWORK ReadOnlySpan<T> current = _unread[..(sizeof(TValue) / sizeof(T) * count)]; value = MemoryMarshal.Cast<T, TValue>(current); #else value = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, TValue>(ref MemoryMarshal.GetReference(_unread)), count); #endif UnsafeAdvance((sizeof(TValue) / sizeof(T)) * count); } return success; } /// <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> public readonly bool IsNext(params ReadOnlySpan<T> next) => _unread.StartsWith(next); /// <summary> /// Advance the reader if the given <paramref name="next"/> values are next. /// </summary> /// <param name="next">The span to compare the next items to.</param> /// <returns><see langword="true"/> if the values were found and the reader advanced.</returns> public bool TryAdvancePast(params ReadOnlySpan<T> next) { bool success = false; if (_unread.StartsWith(next)) { UnsafeAdvance(next.Length); success = true; } return success; } /// <summary> /// Advance the reader past consecutive instances of the given <paramref name="value"/>. /// </summary> /// <returns>How many positions the reader has been advanced</returns> public int AdvancePast(T value) { int count = 0; int index = _unread.IndexOfAnyExcept(value); if (index == -1) { // Everything left is the value count = _unread.Length; _unread = default; } else if (index != 0) { count = index; UnsafeAdvance(index); } return count; } /// <summary> /// Advance the reader by the given <paramref name="count"/>. /// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int count) => _unread = _unread[count..]; /// <summary> /// Rewind the reader by the given <paramref name="count"/>. /// </summary> public void Rewind(int count) => _unread = Span[(Span.Length - _unread.Length - count)..]; /// <summary> /// Reset the reader to the beginning of the span. /// </summary> public void Reset() => _unread = Span; /// <summary> /// Advance the reader without bounds checking. /// </summary> /// <param name="count"></param> [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UnsafeAdvance(int count) { Debug.Assert((uint)count <= (uint)_unread.Length); UncheckedSlice(ref _unread, count, _unread.Length - count); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UncheckedSliceTo(ref ReadOnlySpan<T> span, int length) { Debug.Assert((uint)length <= (uint)span.Length); #if NETFRAMEWORK span = span[..length]; #else span = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(span), length); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void UncheckedSlice(ref ReadOnlySpan<T> span, int start, int length) { Debug.Assert((uint)start <= (uint)span.Length && (uint)length <= (uint)(span.Length - start)); #if NETFRAMEWORK span = span.Slice(start, length); #else span = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref MemoryMarshal.GetReference(span), (nint)(uint)start), length); #endif } }