mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add padding options to U8StringBuilder
This commit is contained in:
parent
6641109d94
commit
6360a18fbf
1 changed files with 282 additions and 82 deletions
|
@ -18,6 +18,17 @@ namespace LibHac.Common
|
|||
public bool Overflowed { get; private set; }
|
||||
public bool AutoExpand { get; }
|
||||
|
||||
private enum PadType : byte
|
||||
{
|
||||
None,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
private PadType PaddingType { get; set; }
|
||||
private byte PaddingCharacter { get; set; }
|
||||
private byte PaddedLength { get; set; }
|
||||
|
||||
private byte[] _rentedArray;
|
||||
|
||||
public readonly int Capacity
|
||||
|
@ -32,6 +43,9 @@ namespace LibHac.Common
|
|||
Length = 0;
|
||||
Overflowed = false;
|
||||
AutoExpand = autoExpand;
|
||||
PaddingType = PadType.None;
|
||||
PaddingCharacter = 0;
|
||||
PaddedLength = 0;
|
||||
_rentedArray = null;
|
||||
|
||||
if (autoExpand)
|
||||
|
@ -61,17 +75,33 @@ namespace LibHac.Common
|
|||
// calls to be chained without accidentally creating a copy of the U8StringBuilder.
|
||||
internal void AppendInternal(ReadOnlySpan<byte> value)
|
||||
{
|
||||
// Once in the Overflowed state, nothing else is written to the buffer.
|
||||
if (Overflowed) return;
|
||||
|
||||
int valueLength = StringUtils.GetLength(value);
|
||||
|
||||
// If needed, expand the buffer size if allowed, or set the Overflowed state if not.
|
||||
if (!TryEnsureAdditionalCapacity(valueLength))
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Because we know the length of the output string beforehand, we can write the
|
||||
// proper amount of padding bytes before writing the output string to the buffer.
|
||||
if (PaddingType == PadType.Left && valueLength < PaddedLength)
|
||||
{
|
||||
int paddingNeeded = PaddedLength - valueLength;
|
||||
Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter);
|
||||
PaddingType = PadType.None;
|
||||
Length += paddingNeeded;
|
||||
}
|
||||
|
||||
// Copy the string to the buffer and right pad if necessary.
|
||||
value.Slice(0, valueLength).CopyTo(Buffer.Slice(Length));
|
||||
PadOutput(valueLength);
|
||||
|
||||
// Update the length and null-terminate the string.
|
||||
Length += valueLength;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
@ -86,11 +116,189 @@ namespace LibHac.Common
|
|||
return;
|
||||
}
|
||||
|
||||
if (PaddingType == PadType.Left && 1 < PaddedLength)
|
||||
{
|
||||
int paddingNeeded = PaddedLength - 1;
|
||||
Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter);
|
||||
PaddingType = PadType.None;
|
||||
Length += paddingNeeded;
|
||||
}
|
||||
|
||||
Buffer[Length] = value;
|
||||
PadOutput(1);
|
||||
|
||||
Length++;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatInt64(long value, ulong mask, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Check if we have enough remaining buffer to fit the required padding.
|
||||
if (!TryEnsureAdditionalCapacity(0))
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove possible sign extension if needed
|
||||
if (mask == 'x' || mask == 'X')
|
||||
{
|
||||
value &= (long)mask;
|
||||
}
|
||||
|
||||
int bytesWritten;
|
||||
|
||||
// If auto-expand is enabled, try to expand the buffer until it can hold the output.
|
||||
while (true)
|
||||
{
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
// Continue if the buffer is large enough.
|
||||
if (bufferLargeEnough)
|
||||
break;
|
||||
|
||||
// We can't expand the buffer. Mark the string builder as Overflowed
|
||||
if (!AutoExpand)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Grow the buffer and try again. The buffer size will at least double each time it grows,
|
||||
// so asking for 0x10 additional bytes actually gets us more than that.
|
||||
Grow(availableBuffer.Length + 0x10);
|
||||
Assert.True(Length + availableBuffer.Length < Capacity);
|
||||
}
|
||||
|
||||
PadOutput(bytesWritten);
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatUInt64(ulong value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
if (!TryEnsureAdditionalCapacity(0))
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesWritten;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (bufferLargeEnough)
|
||||
break;
|
||||
|
||||
if (!AutoExpand)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Grow(availableBuffer.Length + 0x10);
|
||||
Assert.True(Length + availableBuffer.Length < Capacity);
|
||||
}
|
||||
|
||||
PadOutput(bytesWritten);
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatFloat(float value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
if (!TryEnsureAdditionalCapacity(0))
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesWritten;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (bufferLargeEnough)
|
||||
break;
|
||||
|
||||
if (!AutoExpand)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Grow(availableBuffer.Length + 0x10);
|
||||
Assert.True(Length + availableBuffer.Length < Capacity);
|
||||
}
|
||||
|
||||
PadOutput(bytesWritten);
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatDouble(double value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
if (!TryEnsureAdditionalCapacity(0))
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int bytesWritten;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (bufferLargeEnough)
|
||||
break;
|
||||
|
||||
if (!AutoExpand)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Grow(availableBuffer.Length + 0x10);
|
||||
Assert.True(Length + availableBuffer.Length < Capacity);
|
||||
}
|
||||
|
||||
PadOutput(bytesWritten);
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void AppendFormatInternal(byte value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatUInt64(value, format, precision);
|
||||
|
@ -131,6 +339,20 @@ namespace LibHac.Common
|
|||
internal void AppendFormatInternal(double value, char format = 'G', byte precision = 255) =>
|
||||
AppendFormatDouble(value, format, precision);
|
||||
|
||||
internal void PadLeftInternal(byte paddingCharacter, byte paddedLength)
|
||||
{
|
||||
PaddingType = PadType.Left;
|
||||
PaddingCharacter = paddingCharacter;
|
||||
PaddedLength = paddedLength;
|
||||
}
|
||||
|
||||
internal void PadRightInternal(byte paddingCharacter, byte paddedLength)
|
||||
{
|
||||
PaddingType = PadType.Right;
|
||||
PaddingCharacter = paddingCharacter;
|
||||
PaddedLength = paddedLength;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly bool HasCapacity(int requiredCapacity)
|
||||
{
|
||||
|
@ -139,12 +361,14 @@ namespace LibHac.Common
|
|||
|
||||
private bool TryEnsureAdditionalCapacity(int requiredAdditionalCapacity)
|
||||
{
|
||||
bool hasCapacity = HasCapacity(Length + requiredAdditionalCapacity);
|
||||
int paddedLength = PaddingType != PadType.None ? PaddedLength : 0;
|
||||
int requiredAfterPadding = Math.Max(paddedLength, requiredAdditionalCapacity);
|
||||
bool hasCapacity = HasCapacity(Length + requiredAfterPadding);
|
||||
|
||||
if (!hasCapacity && AutoExpand)
|
||||
{
|
||||
Grow(requiredAdditionalCapacity);
|
||||
Assert.True(HasCapacity(Length + requiredAdditionalCapacity));
|
||||
Grow(requiredAfterPadding);
|
||||
Assert.True(HasCapacity(Length + requiredAfterPadding));
|
||||
hasCapacity = true;
|
||||
}
|
||||
|
||||
|
@ -163,6 +387,7 @@ namespace LibHac.Common
|
|||
ArrayPool<byte>.Shared.Rent(Math.Max(Length + requiredAdditionalCapacity, Capacity * 2));
|
||||
|
||||
Buffer.Slice(0, Length).CopyTo(poolArray);
|
||||
Buffer = poolArray;
|
||||
|
||||
byte[] toReturn = _rentedArray;
|
||||
_rentedArray = poolArray;
|
||||
|
@ -177,90 +402,37 @@ namespace LibHac.Common
|
|||
if (Buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0.");
|
||||
}
|
||||
|
||||
private void AppendFormatInt64(long value, ulong mask, char format, byte precision)
|
||||
/// <summary>
|
||||
/// Pads the output after being written to the buffer if necessary,
|
||||
/// resetting the padding type to <see cref="PadType.None"/> afterward.
|
||||
/// </summary>
|
||||
/// <param name="bytesWritten">The length of the unpadded output string.</param>
|
||||
/// <remarks>Because it involves moving the output string when left padding, this function is only used
|
||||
/// when the length of the output string isn't known before writing it to the buffer.</remarks>
|
||||
private void PadOutput(int bytesWritten)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Remove possible sign extension if needed
|
||||
if (mask == 'x' | mask == 'X')
|
||||
if (PaddingType == PadType.Right && bytesWritten < PaddedLength)
|
||||
{
|
||||
value &= (long)mask;
|
||||
// Fill the remaining bytes to the right with the padding character
|
||||
int paddingNeeded = PaddedLength - bytesWritten;
|
||||
Buffer.Slice(Length + bytesWritten, paddingNeeded).Fill(PaddingCharacter);
|
||||
PaddingType = PadType.None;
|
||||
Length += paddingNeeded;
|
||||
}
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
else if (PaddingType == PadType.Left && bytesWritten < PaddedLength)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
int paddingNeeded = PaddedLength - bytesWritten;
|
||||
|
||||
// Move the printed bytes to the right to make room for the padding
|
||||
Span<byte> source = Buffer.Slice(Length, bytesWritten);
|
||||
Span<byte> dest = Buffer.Slice(Length + paddingNeeded, bytesWritten);
|
||||
source.CopyTo(dest);
|
||||
|
||||
// Fill the leading bytes with the padding character
|
||||
Buffer.Slice(Length, paddingNeeded).Fill(PaddingCharacter);
|
||||
PaddingType = PadType.None;
|
||||
Length += paddingNeeded;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatUInt64(ulong value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatFloat(float value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
private void AppendFormatDouble(double value, char format, byte precision)
|
||||
{
|
||||
if (Overflowed) return;
|
||||
|
||||
// Exclude the null terminator from the buffer because Utf8Formatter doesn't handle it
|
||||
Span<byte> availableBuffer = Buffer.Slice(Length, Capacity - Length);
|
||||
|
||||
bool bufferLargeEnough = Utf8Formatter.TryFormat(value, availableBuffer, out int bytesWritten,
|
||||
new StandardFormat(format, precision));
|
||||
|
||||
if (!bufferLargeEnough)
|
||||
{
|
||||
Overflowed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Length += bytesWritten;
|
||||
AddNullTerminator();
|
||||
}
|
||||
|
||||
public override readonly string ToString() => StringUtils.Utf8ZToString(Buffer);
|
||||
|
@ -361,5 +533,33 @@ namespace LibHac.Common
|
|||
sb.AppendFormatInternal(value, format, precision);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parameters used to left-pad the next appended value.
|
||||
/// </summary>
|
||||
/// <param name="sb">The used string builder.</param>
|
||||
/// <param name="paddingCharacter">The character used to pad the output.</param>
|
||||
/// <param name="paddedLength">The minimum length of the output after padding.</param>
|
||||
/// <returns>The used string builder.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder PadLeft(this ref U8StringBuilder sb, byte paddingCharacter, byte paddedLength)
|
||||
{
|
||||
sb.PadLeftInternal(paddingCharacter, paddedLength);
|
||||
return ref sb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the parameters used to right-pad the next appended value.
|
||||
/// </summary>
|
||||
/// <param name="sb">The used string builder.</param>
|
||||
/// <param name="paddingCharacter">The character used to pad the output.</param>
|
||||
/// <param name="paddedLength">The minimum length of the output after padding.</param>
|
||||
/// <returns>The used string builder.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref U8StringBuilder PadRight(this ref U8StringBuilder sb, byte paddingCharacter, byte paddedLength)
|
||||
{
|
||||
sb.PadRightInternal(paddingCharacter, paddedLength);
|
||||
return ref sb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue