Skip to content

Commit

Permalink
Merge pull request #2 from RichardD2/main
Browse files Browse the repository at this point in the history
Add .NET Standard 2.0 target.
  • Loading branch information
hadashiA committed Nov 16, 2023
2 parents 0acb4ca + eaca0eb commit 43bacb3
Show file tree
Hide file tree
Showing 10 changed files with 420 additions and 73 deletions.
256 changes: 256 additions & 0 deletions src/Utf8StringInterpolation/ArrayBufferWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
#if NET6_0_OR_GREATER || NETSTANDARD2_1

using System.Runtime.CompilerServices;
[assembly: TypeForwardedTo(typeof(System.Buffers.ArrayBufferWriter<>))]

#else

using System;
using System.Diagnostics;

namespace System.Buffers
{
/// <summary>
/// Represents a heap-based, array-backed output sink into which <typeparamref name="T"/> data can be written.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class ArrayBufferWriter<T> : IBufferWriter<T>
{
// Copy of Array.MaxLength.
// Used by projects targeting .NET Framework.
private const int ArrayMaxLength = 0x7FFFFFC7;

private const int DefaultInitialBufferSize = 256;

private T[] _buffer;
private int _index;


/// <summary>
/// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to,
/// with the default initial capacity.
/// </summary>
public ArrayBufferWriter()
{
_buffer = Array.Empty<T>();
_index = 0;
}

/// <summary>
/// Creates an instance of an <see cref="ArrayBufferWriter{T}"/>, in which data can be written to,
/// with an initial capacity specified.
/// </summary>
/// <param name="initialCapacity">The minimum capacity with which to initialize the underlying buffer.</param>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="initialCapacity"/> is not positive (i.e. less than or equal to 0).
/// </exception>
public ArrayBufferWriter(int initialCapacity)
{
if (initialCapacity <= 0)
throw new ArgumentException(null, nameof(initialCapacity));

_buffer = new T[initialCapacity];
_index = 0;
}

/// <summary>
/// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlyMemory{T}"/>.
/// </summary>
public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, _index);

/// <summary>
/// Returns the data written to the underlying buffer so far, as a <see cref="ReadOnlySpan{T}"/>.
/// </summary>
public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, _index);

/// <summary>
/// Returns the amount of data written to the underlying buffer so far.
/// </summary>
public int WrittenCount => _index;

/// <summary>
/// Returns the total amount of space within the underlying buffer.
/// </summary>
public int Capacity => _buffer.Length;

/// <summary>
/// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow.
/// </summary>
public int FreeCapacity => _buffer.Length - _index;

/// <summary>
/// Clears the data written to the underlying buffer.
/// </summary>
/// <remarks>
/// <para>
/// You must reset or clear the <see cref="ArrayBufferWriter{T}"/> before trying to re-use it.
/// </para>
/// <para>
/// The <see cref="ResetWrittenCount"/> method is faster since it only sets to zero the writer's index
/// while the <see cref="Clear"/> method additionally zeroes the content of the underlying buffer.
/// </para>
/// </remarks>
/// <seealso cref="ResetWrittenCount"/>
public void Clear()
{
Debug.Assert(_buffer.Length >= _index);
_buffer.AsSpan(0, _index).Clear();
_index = 0;
}

/// <summary>
/// Resets the data written to the underlying buffer without zeroing its content.
/// </summary>
/// <remarks>
/// <para>
/// You must reset or clear the <see cref="ArrayBufferWriter{T}"/> before trying to re-use it.
/// </para>
/// <para>
/// If you reset the writer using the <see cref="ResetWrittenCount"/> method, the underlying buffer will not be cleared.
/// </para>
/// </remarks>
/// <seealso cref="Clear"/>
public void ResetWrittenCount() => _index = 0;

/// <summary>
/// Notifies <see cref="IBufferWriter{T}"/> that <paramref name="count"/> amount of data was written to the output <see cref="Span{T}"/>/<see cref="Memory{T}"/>
/// </summary>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="count"/> is negative.
/// </exception>
/// <exception cref="InvalidOperationException">
/// Thrown when attempting to advance past the end of the underlying buffer.
/// </exception>
/// <remarks>
/// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
/// </remarks>
public void Advance(int count)
{
if (count < 0)
throw new ArgumentException(null, nameof(count));

if (_index > _buffer.Length - count)
ThrowInvalidOperationException_AdvancedTooFar(_buffer.Length);

_index += count;
}

/// <summary>
/// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
/// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned.
/// </summary>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="sizeHint"/> is negative.
/// </exception>
/// <remarks>
/// <para>
/// This will never return an empty <see cref="Memory{T}"/>.
/// </para>
/// <para>
/// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
/// </para>
/// <para>
/// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
/// </para>
/// <para>
/// If you reset the writer using the <see cref="ResetWrittenCount"/> method, this method may return a non-cleared <see cref="Memory{T}"/>.
/// </para>
/// <para>
/// If you clear the writer using the <see cref="Clear"/> method, this method will return a <see cref="Memory{T}"/> with its content zeroed.
/// </para>
/// </remarks>
public Memory<T> GetMemory(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
Debug.Assert(_buffer.Length > _index);
return _buffer.AsMemory(_index);
}

/// <summary>
/// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
/// If no <paramref name="sizeHint"/> is provided (or it's equal to <code>0</code>), some non-empty buffer is returned.
/// </summary>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="sizeHint"/> is negative.
/// </exception>
/// <remarks>
/// <para>
/// This will never return an empty <see cref="Span{T}"/>.
/// </para>
/// <para>
/// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
/// </para>
/// <para>
/// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer.
/// </para>
/// <para>
/// If you reset the writer using the <see cref="ResetWrittenCount"/> method, this method may return a non-cleared <see cref="Span{T}"/>.
/// </para>
/// <para>
/// If you clear the writer using the <see cref="Clear"/> method, this method will return a <see cref="Span{T}"/> with its content zeroed.
/// </para>
/// </remarks>
public Span<T> GetSpan(int sizeHint = 0)
{
CheckAndResizeBuffer(sizeHint);
Debug.Assert(_buffer.Length > _index);
return _buffer.AsSpan(_index);
}

private void CheckAndResizeBuffer(int sizeHint)
{
if (sizeHint < 0)
throw new ArgumentException(nameof(sizeHint));

if (sizeHint == 0)
{
sizeHint = 1;
}

if (sizeHint > FreeCapacity)
{
int currentLength = _buffer.Length;

// Attempt to grow by the larger of the sizeHint and double the current size.
int growBy = Math.Max(sizeHint, currentLength);

if (currentLength == 0)
{
growBy = Math.Max(growBy, DefaultInitialBufferSize);
}

int newSize = currentLength + growBy;

if ((uint)newSize > int.MaxValue)
{
// Attempt to grow to ArrayMaxLength.
uint needed = (uint)(currentLength - FreeCapacity + sizeHint);
Debug.Assert(needed > currentLength);

if (needed > ArrayMaxLength)
{
ThrowOutOfMemoryException(needed);
}

newSize = ArrayMaxLength;
}

Array.Resize(ref _buffer, newSize);
}

Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint);
}

private static void ThrowInvalidOperationException_AdvancedTooFar(int capacity)
{
throw new InvalidOperationException();
}

private static void ThrowOutOfMemoryException(uint capacity)
{
throw new OutOfMemoryException();
}
}
}

#endif
91 changes: 91 additions & 0 deletions src/Utf8StringInterpolation/Shims.NetStandard2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#if NETSTANDARD2_0

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Utf8StringInterpolation
{
internal static partial class Shims
{
public static string GetString(this Encoding encoding, scoped ReadOnlySpan<byte> bytes)
{
if (bytes.IsEmpty) return string.Empty;

unsafe
{
fixed (byte* pB = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetString(pB, bytes.Length);
}
}
}

public static int GetByteCount(this Encoding encoding, scoped ReadOnlySpan<char> chars)
{
unsafe
{
fixed (char* charsPtr = &MemoryMarshal.GetReference(chars))
{
return encoding.GetByteCount(charsPtr, chars.Length);
}
}
}

public static int GetBytes(this Encoding encoding, scoped ReadOnlySpan<char> chars, scoped Span<byte> bytes)
{
unsafe
{
fixed (char* charsPtr = &MemoryMarshal.GetReference(chars))
fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length);
}
}
}

private static bool TryFormat(this DateTime value, scoped Span<char> destination, out int charsWritten, string? format, IFormatProvider? formatProvider)
{
string s = value.ToString(format, formatProvider);
if (s.Length > destination.Length)
{
charsWritten = 0;
return false;
}

s.AsSpan().CopyTo(destination);
charsWritten = s.Length;
return true;
}

private static bool TryFormat(this DateTimeOffset value, scoped Span<char> destination, out int charsWritten, string? format, IFormatProvider? formatProvider)
{
string s = value.ToString(format, formatProvider);
if (s.Length > destination.Length)
{
charsWritten = 0;
return false;
}

s.AsSpan().CopyTo(destination);
charsWritten = s.Length;
return true;
}

private static bool TryFormat(this TimeSpan value, scoped Span<char> destination, out int charsWritten, string? format, IFormatProvider? formatProvider)
{
string s = value.ToString(format, formatProvider);
if (s.Length > destination.Length)
{
charsWritten = 0;
return false;
}

s.AsSpan().CopyTo(destination);
charsWritten = s.Length;
return true;
}
}
}

#endif
Loading

0 comments on commit 43bacb3

Please sign in to comment.