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 Overflowed { get; private set; }
|
||||||
public bool AutoExpand { get; }
|
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;
|
private byte[] _rentedArray;
|
||||||
|
|
||||||
public readonly int Capacity
|
public readonly int Capacity
|
||||||
|
@ -32,6 +43,9 @@ namespace LibHac.Common
|
||||||
Length = 0;
|
Length = 0;
|
||||||
Overflowed = false;
|
Overflowed = false;
|
||||||
AutoExpand = autoExpand;
|
AutoExpand = autoExpand;
|
||||||
|
PaddingType = PadType.None;
|
||||||
|
PaddingCharacter = 0;
|
||||||
|
PaddedLength = 0;
|
||||||
_rentedArray = null;
|
_rentedArray = null;
|
||||||
|
|
||||||
if (autoExpand)
|
if (autoExpand)
|
||||||
|
@ -61,17 +75,33 @@ namespace LibHac.Common
|
||||||
// calls to be chained without accidentally creating a copy of the U8StringBuilder.
|
// calls to be chained without accidentally creating a copy of the U8StringBuilder.
|
||||||
internal void AppendInternal(ReadOnlySpan<byte> value)
|
internal void AppendInternal(ReadOnlySpan<byte> value)
|
||||||
{
|
{
|
||||||
|
// Once in the Overflowed state, nothing else is written to the buffer.
|
||||||
if (Overflowed) return;
|
if (Overflowed) return;
|
||||||
|
|
||||||
int valueLength = StringUtils.GetLength(value);
|
int valueLength = StringUtils.GetLength(value);
|
||||||
|
|
||||||
|
// If needed, expand the buffer size if allowed, or set the Overflowed state if not.
|
||||||
if (!TryEnsureAdditionalCapacity(valueLength))
|
if (!TryEnsureAdditionalCapacity(valueLength))
|
||||||
{
|
{
|
||||||
Overflowed = true;
|
Overflowed = true;
|
||||||
return;
|
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));
|
value.Slice(0, valueLength).CopyTo(Buffer.Slice(Length));
|
||||||
|
PadOutput(valueLength);
|
||||||
|
|
||||||
|
// Update the length and null-terminate the string.
|
||||||
Length += valueLength;
|
Length += valueLength;
|
||||||
AddNullTerminator();
|
AddNullTerminator();
|
||||||
}
|
}
|
||||||
|
@ -86,11 +116,189 @@ namespace LibHac.Common
|
||||||
return;
|
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;
|
Buffer[Length] = value;
|
||||||
|
PadOutput(1);
|
||||||
|
|
||||||
Length++;
|
Length++;
|
||||||
AddNullTerminator();
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
internal void AppendFormatInternal(byte value, char format = 'G', byte precision = 255) =>
|
internal void AppendFormatInternal(byte value, char format = 'G', byte precision = 255) =>
|
||||||
AppendFormatUInt64(value, format, precision);
|
AppendFormatUInt64(value, format, precision);
|
||||||
|
@ -131,6 +339,20 @@ namespace LibHac.Common
|
||||||
internal void AppendFormatInternal(double value, char format = 'G', byte precision = 255) =>
|
internal void AppendFormatInternal(double value, char format = 'G', byte precision = 255) =>
|
||||||
AppendFormatDouble(value, format, precision);
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private readonly bool HasCapacity(int requiredCapacity)
|
private readonly bool HasCapacity(int requiredCapacity)
|
||||||
{
|
{
|
||||||
|
@ -139,12 +361,14 @@ namespace LibHac.Common
|
||||||
|
|
||||||
private bool TryEnsureAdditionalCapacity(int requiredAdditionalCapacity)
|
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)
|
if (!hasCapacity && AutoExpand)
|
||||||
{
|
{
|
||||||
Grow(requiredAdditionalCapacity);
|
Grow(requiredAfterPadding);
|
||||||
Assert.True(HasCapacity(Length + requiredAdditionalCapacity));
|
Assert.True(HasCapacity(Length + requiredAfterPadding));
|
||||||
hasCapacity = true;
|
hasCapacity = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +387,7 @@ namespace LibHac.Common
|
||||||
ArrayPool<byte>.Shared.Rent(Math.Max(Length + requiredAdditionalCapacity, Capacity * 2));
|
ArrayPool<byte>.Shared.Rent(Math.Max(Length + requiredAdditionalCapacity, Capacity * 2));
|
||||||
|
|
||||||
Buffer.Slice(0, Length).CopyTo(poolArray);
|
Buffer.Slice(0, Length).CopyTo(poolArray);
|
||||||
|
Buffer = poolArray;
|
||||||
|
|
||||||
byte[] toReturn = _rentedArray;
|
byte[] toReturn = _rentedArray;
|
||||||
_rentedArray = poolArray;
|
_rentedArray = poolArray;
|
||||||
|
@ -177,90 +402,37 @@ namespace LibHac.Common
|
||||||
if (Buffer.Length == 0) throw new ArgumentException("Buffer length must be greater than 0.");
|
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;
|
if (PaddingType == PadType.Right && bytesWritten < PaddedLength)
|
||||||
|
|
||||||
// Remove possible sign extension if needed
|
|
||||||
if (mask == 'x' | mask == 'X')
|
|
||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
else if (PaddingType == PadType.Left && bytesWritten < PaddedLength)
|
||||||
// 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;
|
int paddingNeeded = PaddedLength - bytesWritten;
|
||||||
return;
|
|
||||||
|
// 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);
|
public override readonly string ToString() => StringUtils.Utf8ZToString(Buffer);
|
||||||
|
@ -361,5 +533,33 @@ namespace LibHac.Common
|
||||||
sb.AppendFormatInternal(value, format, precision);
|
sb.AppendFormatInternal(value, format, precision);
|
||||||
return ref sb;
|
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