using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text;
// Leaving this in this namespace because I only need this class here to work around maths being a separate assembly.
// ReSharper disable once CheckNamespace
namespace Robust.Shared.Utility;
///
/// Helpers for dealing with string formatting and related things.
///
public static class FormatHelpers
{
///
/// Format a string interpolation into a given buffer. If the buffer is not large enough, the result is truncated.
///
///
/// Assuming everything you're formatting with implements , this should be zero-alloc.
///
/// The buffer to format into.
/// String interpolation handler to implement buffer formatting logic.
/// The amount of chars written into the buffer.
public static int FormatInto(
Span buffer,
[InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler)
{
return handler.Length;
}
///
/// Tries to format a string interpolation into a given buffer.
/// If the buffer is not large enough, the result is truncated.
///
///
///
/// This is intended to be used as an easy implementation of .
///
///
/// Assuming everything you're formatting with implements ,
/// this should be zero-alloc on release.
///
///
/// The buffer to format into.
/// The amount of chars written into the buffer.
/// String interpolation handler to implement buffer formatting logic.
/// False if the formatting failed due to lack of space and was truncated.
public static bool TryFormatInto(
Span buffer,
out int charsWritten,
[InterpolatedStringHandlerArgument("buffer")] ref BufferInterpolatedStringHandler handler)
{
charsWritten = handler.Length;
return !handler.Truncated;
}
///
/// Format a string interpolation into a given memory buffer.
/// If the buffer is not large enough, the result is truncated.
///
/// The memory buffer to format into.
/// String interpolation handler to implement buffer formatting logic.
/// The region of memory filled by the formatting operation.
public static Memory FormatIntoMem(
Memory buffer,
[InterpolatedStringHandlerArgument("buffer")] ref MemoryBufferInterpolatedStringHandler handler)
{
return buffer[..handler.Handler.Length];
}
///
/// Copy the contents of a into a memory buffer, giving back the subregion used.
/// If the buffer is not enough space, the result is truncated.
///
/// The to copy from
/// The memory buffer to copy into.
/// The memory region actually used by the copied data.
public static Memory BuilderToMemory(StringBuilder builder, Memory memory)
{
var truncLength = Math.Min(builder.Length, memory.Length);
builder.CopyTo(0, memory.Span, truncLength);
return memory[..truncLength];
}
}
///
/// Interpolated string handler used by .
///
[InterpolatedStringHandler]
public ref struct BufferInterpolatedStringHandler
{
private Span _buffer;
internal int Length;
internal bool Truncated;
[SuppressMessage("ReSharper", "UnusedParameter.Local")]
public BufferInterpolatedStringHandler(int literalLength, int formattedCount, Span buffer)
{
_buffer = buffer;
Length = 0;
Truncated = false;
}
public void AppendLiteral(string literal)
{
AppendString(literal);
}
public void AppendFormatted(ReadOnlySpan value)
{
AppendString(value);
}
[SuppressMessage("ReSharper", "MergeCastWithTypeCheck")]
public void AppendFormatted(T value)
{
if (value is ISpanFormattable)
{
// JIT is able to avoid boxing due to call structure.
((ISpanFormattable)value).TryFormat(_buffer, out var written, default, null);
Advance(written);
return;
}
var str = value?.ToString();
if (str != null)
AppendString(str);
}
[SuppressMessage("ReSharper", "MergeCastWithTypeCheck")]
public void AppendFormatted(T value, string format)
{
string? str;
if (value is IFormattable)
{
if (value is ISpanFormattable)
{
// JIT is able to avoid boxing due to call structure.
Truncated |= !((ISpanFormattable)value).TryFormat(_buffer, out var written, format, null);
Advance(written);
return;
}
// JIT is able to avoid boxing due to call structure.
str = ((IFormattable)value).ToString(format, null);
}
else
{
str = value?.ToString();
}
if (str != null)
AppendString(str);
}
private void AppendString(ReadOnlySpan value)
{
var copyLength = value.Length;
if (copyLength > _buffer.Length)
{
copyLength = _buffer.Length;
Truncated = true;
}
value[..copyLength].CopyTo(_buffer);
Advance(copyLength);
}
private void Advance(int amount)
{
_buffer = _buffer[amount..];
Length += amount;
}
}
///
/// Interpolated string handler used by .
///
///
/// Only exists as workaround for https://youtrack.jetbrains.com/issue/RIDER-78472.
///
[InterpolatedStringHandler]
public ref struct MemoryBufferInterpolatedStringHandler
{
public BufferInterpolatedStringHandler Handler;
public MemoryBufferInterpolatedStringHandler(int literalLength, int formattedCount, Memory buffer)
{
Handler = new BufferInterpolatedStringHandler(literalLength, formattedCount, buffer.Span);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendLiteral(string literal) => Handler.AppendLiteral(literal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendFormatted(ReadOnlySpan value) => Handler.AppendFormatted(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendFormatted(T value) => Handler.AppendFormatted(value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendFormatted(T value, string format) => Handler.AppendFormatted(value, format);
}