2467 lines
96 KiB
C#
2467 lines
96 KiB
C#
// Copyright (c) 2015-2017 Michael Popoloski
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
using System.Globalization;
|
|
using System.Reflection;
|
|
using System.Runtime.CompilerServices;
|
|
using UnsafeIL;
|
|
|
|
namespace System.Text.Formatting {
|
|
/// <summary>
|
|
/// Specifies an interface for types that act as a set of formatting arguments.
|
|
/// </summary>
|
|
public interface IArgSet {
|
|
/// <summary>
|
|
/// The number of arguments in the set.
|
|
/// </summary>
|
|
int Count { get; }
|
|
|
|
/// <summary>
|
|
/// Format one of the arguments in the set into the given string buffer.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to which to append the argument.</param>
|
|
/// <param name="index">The index of the argument to format.</param>
|
|
/// <param name="format">A specifier indicating how the argument should be formatted.</param>
|
|
void Format (StringBuffer buffer, int index, StringView format);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Defines an interface for types that can be formatted into a string buffer.
|
|
/// </summary>
|
|
public interface IStringFormattable {
|
|
/// <summary>
|
|
/// Format the current instance into the given string buffer.
|
|
/// </summary>
|
|
/// <param name="buffer">The buffer to which to append.</param>
|
|
/// <param name="format">A specifier indicating how the argument should be formatted.</param>
|
|
void Format (StringBuffer buffer, StringView format);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A low-allocation version of the built-in <see cref="StringBuilder"/> type.
|
|
/// </summary>
|
|
public unsafe sealed partial class StringBuffer {
|
|
CachedCulture culture;
|
|
char[] buffer;
|
|
int currentCount;
|
|
|
|
/// <summary>
|
|
/// The number of characters in the buffer.
|
|
/// </summary>
|
|
public int Count {
|
|
get { return currentCount; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The culture used to format string data.
|
|
/// </summary>
|
|
public CultureInfo Culture {
|
|
get { return culture.Culture; }
|
|
set {
|
|
if (culture.Culture == value)
|
|
return;
|
|
|
|
if (value == CultureInfo.InvariantCulture)
|
|
culture = CachedInvariantCulture;
|
|
else if (value == CachedCurrentCulture.Culture)
|
|
culture = CachedCurrentCulture;
|
|
else
|
|
culture = new CachedCulture(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="StringBuffer"/> class.
|
|
/// </summary>
|
|
public StringBuffer ()
|
|
: this(DefaultCapacity) {
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="StringBuffer"/> class.
|
|
/// </summary>
|
|
/// <param name="capacity">The initial size of the string buffer.</param>
|
|
public StringBuffer (int capacity) {
|
|
buffer = new char[capacity];
|
|
culture = CachedCurrentCulture;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a custom formatter to use when converting instances of a given type to a string.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type for which to set the formatter.</typeparam>
|
|
/// <param name="formatter">A delegate that will be called to format instances of the specified type.</param>
|
|
public static void SetCustomFormatter<T>(Action<StringBuffer, T, StringView> formatter) {
|
|
ValueHelper<T>.Formatter = formatter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the buffer.
|
|
/// </summary>
|
|
public void Clear () {
|
|
currentCount = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the contents of the buffer to the given array.
|
|
/// </summary>
|
|
/// <param name="sourceIndex">The index within the buffer to begin copying.</param>
|
|
/// <param name="destination">The destination array.</param>
|
|
/// <param name="destinationIndex">The index within the destination array to which to begin copying.</param>
|
|
/// <param name="count">The number of characters to copy.</param>
|
|
public void CopyTo (int sourceIndex, char[] destination, int destinationIndex, int count) {
|
|
if (destination == null)
|
|
throw new ArgumentNullException(nameof(destination));
|
|
if (destinationIndex + count > destination.Length || destinationIndex < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(destinationIndex));
|
|
|
|
fixed (char* destPtr = &destination[destinationIndex])
|
|
CopyTo(destPtr, sourceIndex, count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the contents of the buffer to the given array.
|
|
/// </summary>
|
|
/// <param name="dest">A pointer to the destination array.</param>
|
|
/// <param name="sourceIndex">The index within the buffer to begin copying.</param>
|
|
/// <param name="count">The number of characters to copy.</param>
|
|
public void CopyTo (char* dest, int sourceIndex, int count) {
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
if (sourceIndex + count > currentCount || sourceIndex < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(sourceIndex));
|
|
|
|
fixed (char* s = buffer)
|
|
{
|
|
var src = s + sourceIndex;
|
|
for (int i = 0; i < count; i++)
|
|
*dest++ = *src++;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the contents of the buffer to the given byte array.
|
|
/// </summary>
|
|
/// <param name="dest">A pointer to the destination byte array.</param>
|
|
/// <param name="sourceIndex">The index within the buffer to begin copying.</param>
|
|
/// <param name="count">The number of characters to copy.</param>
|
|
/// <param name="encoding">The encoding to use to convert characters to bytes.</param>
|
|
/// <returns>The number of bytes written to the destination.</returns>
|
|
public int CopyTo (byte* dest, int sourceIndex, int count, Encoding encoding) {
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
if (sourceIndex + count > currentCount || sourceIndex < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(sourceIndex));
|
|
if (encoding == null)
|
|
throw new ArgumentNullException(nameof(encoding));
|
|
|
|
fixed (char* s = buffer)
|
|
return encoding.GetBytes(s, count, dest, count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the buffer to a string instance.
|
|
/// </summary>
|
|
/// <returns>A new string representing the characters currently in the buffer.</returns>
|
|
public override string ToString () {
|
|
return new string(buffer, 0, currentCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends a character to the current buffer.
|
|
/// </summary>
|
|
/// <param name="c">The character to append.</param>
|
|
public void Append (char c) {
|
|
Append(c, 1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends a character to the current buffer several times.
|
|
/// </summary>
|
|
/// <param name="c">The character to append.</param>
|
|
/// <param name="count">The number of times to append the character.</param>
|
|
public void Append (char c, int count) {
|
|
if (count < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(count));
|
|
|
|
CheckCapacity(count);
|
|
fixed (char* b = &buffer[currentCount])
|
|
{
|
|
var ptr = b;
|
|
for (int i = 0; i < count; i++)
|
|
*ptr++ = c;
|
|
currentCount += count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
public void Append (string value) {
|
|
if (value == null)
|
|
throw new ArgumentNullException(nameof(value));
|
|
|
|
Append(value, 0, value.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends a string subset to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The string to append.</param>
|
|
/// <param name="startIndex">The starting index within the string to begin reading characters.</param>
|
|
/// <param name="count">The number of characters to append.</param>
|
|
public void Append (string value, int startIndex, int count) {
|
|
if (value == null)
|
|
throw new ArgumentNullException(nameof(value));
|
|
if (startIndex < 0 || startIndex + count > value.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(startIndex));
|
|
|
|
fixed (char* s = value)
|
|
Append(s + startIndex, count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends an array of characters to the current buffer.
|
|
/// </summary>
|
|
/// <param name="values">The characters to append.</param>
|
|
/// <param name="startIndex">The starting index within the array to begin reading characters.</param>
|
|
/// <param name="count">The number of characters to append.</param>
|
|
public void Append (char[] values, int startIndex, int count) {
|
|
if (values == null)
|
|
throw new ArgumentNullException(nameof(values));
|
|
if (startIndex < 0 || startIndex + count > values.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(startIndex));
|
|
|
|
fixed (char* s = &values[startIndex])
|
|
Append(s, count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends an array of characters to the current buffer.
|
|
/// </summary>
|
|
/// <param name="str">A pointer to the array of characters to append.</param>
|
|
/// <param name="count">The number of characters to append.</param>
|
|
public void Append (char* str, int count) {
|
|
CheckCapacity(count);
|
|
fixed (char* b = &buffer[currentCount])
|
|
{
|
|
var dest = b;
|
|
for (int i = 0; i < count; i++)
|
|
*dest++ = *str++;
|
|
currentCount += count;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
public void Append (bool value) {
|
|
if (value)
|
|
Append(TrueLiteral);
|
|
else
|
|
Append(FalseLiteral);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (sbyte value, StringView format) {
|
|
Numeric.FormatSByte(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (byte value, StringView format) {
|
|
// widening here is fine
|
|
Numeric.FormatUInt32(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (short value, StringView format) {
|
|
Numeric.FormatInt16(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (ushort value, StringView format) {
|
|
// widening here is fine
|
|
Numeric.FormatUInt32(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (int value, StringView format) {
|
|
Numeric.FormatInt32(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (uint value, StringView format) {
|
|
Numeric.FormatUInt32(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (long value, StringView format) {
|
|
Numeric.FormatInt64(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (ulong value, StringView format) {
|
|
Numeric.FormatUInt64(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (float value, StringView format) {
|
|
Numeric.FormatSingle(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (double value, StringView format) {
|
|
Numeric.FormatDouble(this, value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the specified value as a string to the current buffer.
|
|
/// </summary>
|
|
/// <param name="value">The value to append.</param>
|
|
/// <param name="format">A format specifier indicating how to convert <paramref name="value"/> to a string.</param>
|
|
public void Append (decimal value, StringView format) {
|
|
Numeric.FormatDecimal(this, (uint*)&value, format, culture);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance.
|
|
/// Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of argument set being formatted.</typeparam>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="args">The set of args to insert into the format string.</param>
|
|
public void AppendArgSet<T>(string format, ref T args) where T : IArgSet {
|
|
if (format == null)
|
|
throw new ArgumentNullException(nameof(format));
|
|
|
|
fixed (char* formatPtr = format)
|
|
{
|
|
var curr = formatPtr;
|
|
var end = curr + format.Length;
|
|
var segmentsLeft = false;
|
|
var prevArgIndex = 0;
|
|
do {
|
|
CheckCapacity((int)(end - curr) + 1);
|
|
fixed (char* bufferPtr = &buffer[currentCount])
|
|
segmentsLeft = AppendSegment(ref curr, end, bufferPtr, ref prevArgIndex, ref args);
|
|
}
|
|
while (segmentsLeft);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
void CheckCapacity (int count) {
|
|
int newCount = currentCount + count;
|
|
if (newCount > buffer.Length) {
|
|
int newCapacity = buffer.Length * 2;
|
|
if (newCapacity < newCount) newCapacity = newCount;
|
|
char[] newBuffer = new char[newCapacity];
|
|
fixed (void* nptr = newBuffer, ptr = buffer) {
|
|
Unsafe.CopyBlock(nptr, ptr, (uint)(currentCount * sizeof(char)));
|
|
}
|
|
buffer = newBuffer;
|
|
}
|
|
}
|
|
|
|
bool AppendSegment<T>(ref char* currRef, char* end, char* dest, ref int prevArgIndex, ref T args) where T : IArgSet {
|
|
char* curr = currRef;
|
|
char c = '\x0';
|
|
while (curr < end) {
|
|
c = *curr++;
|
|
if (c == '}') {
|
|
// check for escape character for }}
|
|
if (curr < end && *curr == '}')
|
|
curr++;
|
|
else
|
|
ThrowError();
|
|
}
|
|
else if (c == '{') {
|
|
// check for escape character for {{
|
|
if (curr == end)
|
|
ThrowError();
|
|
else if (*curr == '{')
|
|
curr++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
*dest++ = c;
|
|
currentCount++;
|
|
}
|
|
|
|
if (curr == end)
|
|
return false;
|
|
|
|
int index;
|
|
if (*curr == '}')
|
|
index = prevArgIndex;
|
|
else
|
|
index = ParseNum(ref curr, end, MaxArgs);
|
|
if (index >= args.Count)
|
|
throw new FormatException(string.Format(SR.ArgIndexOutOfRange, index));
|
|
|
|
// check for a spacing specifier
|
|
c = SkipWhitespace(ref curr, end);
|
|
var width = 0;
|
|
var leftJustify = false;
|
|
var oldCount = currentCount;
|
|
if (c == ',') {
|
|
curr++;
|
|
c = SkipWhitespace(ref curr, end);
|
|
|
|
// spacing can be left-justified
|
|
if (c == '-') {
|
|
leftJustify = true;
|
|
curr++;
|
|
if (curr == end)
|
|
ThrowError();
|
|
}
|
|
|
|
width = ParseNum(ref curr, end, MaxSpacing);
|
|
c = SkipWhitespace(ref curr, end);
|
|
}
|
|
|
|
// check for format specifier
|
|
curr++;
|
|
if (c == ':') {
|
|
var specifierBuffer = stackalloc char[MaxSpecifierSize];
|
|
var specifierEnd = specifierBuffer + MaxSpecifierSize;
|
|
var specifierPtr = specifierBuffer;
|
|
|
|
while (true) {
|
|
if (curr == end)
|
|
ThrowError();
|
|
|
|
c = *curr++;
|
|
if (c == '{') {
|
|
// check for escape character for {{
|
|
if (curr < end && *curr == '{')
|
|
curr++;
|
|
else
|
|
ThrowError();
|
|
}
|
|
else if (c == '}') {
|
|
// check for escape character for }}
|
|
if (curr < end && *curr == '}')
|
|
curr++;
|
|
else {
|
|
// found the end of the specifier
|
|
// kick off the format job
|
|
var specifier = new StringView(specifierBuffer, (int)(specifierPtr - specifierBuffer));
|
|
args.Format(this, index, specifier);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (specifierPtr == specifierEnd)
|
|
ThrowError();
|
|
*specifierPtr++ = c;
|
|
}
|
|
}
|
|
else {
|
|
// no specifier. make sure we're at the end of the format block
|
|
if (c != '}')
|
|
ThrowError();
|
|
|
|
// format without any specifier
|
|
args.Format(this, index, StringView.Empty);
|
|
}
|
|
|
|
// finish off padding, if necessary
|
|
var padding = width - (currentCount - oldCount);
|
|
if (padding > 0) {
|
|
if (leftJustify)
|
|
Append(' ', padding);
|
|
else {
|
|
// copy the recently placed chars up in memory to make room for padding
|
|
CheckCapacity(padding);
|
|
for (int i = currentCount - 1; i >= oldCount; i--)
|
|
buffer[i + padding] = buffer[i];
|
|
|
|
// fill in padding
|
|
for (int i = 0; i < padding; i++)
|
|
buffer[i + oldCount] = ' ';
|
|
currentCount += padding;
|
|
}
|
|
}
|
|
|
|
prevArgIndex = index + 1;
|
|
currRef = curr;
|
|
return true;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
internal void AppendGeneric<T>(T value, StringView format) {
|
|
// this looks gross, but T is known at JIT-time so this call tree
|
|
// gets compiled down to a direct call with no branching
|
|
if (typeof(T) == typeof(sbyte))
|
|
Append(*(sbyte*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(byte))
|
|
Append(*(byte*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(short))
|
|
Append(*(short*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(ushort))
|
|
Append(*(ushort*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(int))
|
|
Append(*(int*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(uint))
|
|
Append(*(uint*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(long))
|
|
Append(*(long*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(ulong))
|
|
Append(*(ulong*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(float))
|
|
Append(*(float*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(double))
|
|
Append(*(double*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(decimal))
|
|
Append(*(decimal*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(bool))
|
|
Append(*(bool*)Unsafe.AsPointer(ref value));
|
|
else if (typeof(T) == typeof(char))
|
|
Append(*(char*)Unsafe.AsPointer(ref value), format);
|
|
else if (typeof(T) == typeof(string))
|
|
Append(Unsafe.As<string>(value));
|
|
else {
|
|
// first, check to see if it's a value type implementing IStringFormattable
|
|
var formatter = ValueHelper<T>.Formatter;
|
|
if (formatter != null)
|
|
formatter(this, value, format);
|
|
else {
|
|
// We could handle this case by calling ToString() on the object and paying the
|
|
// allocation, but presumably if the user is using us instead of the built-in
|
|
// formatting utilities they would rather be notified of this case, so we'll throw.
|
|
throw new InvalidOperationException(string.Format(SR.TypeNotFormattable, typeof(T)));
|
|
}
|
|
}
|
|
}
|
|
|
|
static int ParseNum (ref char* currRef, char* end, int maxValue) {
|
|
char* curr = currRef;
|
|
char c = *curr;
|
|
if (c < '0' || c > '9')
|
|
ThrowError();
|
|
|
|
int value = 0;
|
|
do {
|
|
value = value * 10 + c - '0';
|
|
curr++;
|
|
if (curr == end)
|
|
ThrowError();
|
|
c = *curr;
|
|
} while (c >= '0' && c <= '9' && value < maxValue);
|
|
|
|
currRef = curr;
|
|
return value;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
static char SkipWhitespace (ref char* currRef, char* end) {
|
|
char* curr = currRef;
|
|
while (curr < end && *curr == ' ') curr++;
|
|
|
|
if (curr == end)
|
|
ThrowError();
|
|
|
|
currRef = curr;
|
|
return *curr;
|
|
}
|
|
|
|
static void ThrowError () {
|
|
throw new FormatException(SR.InvalidFormatString);
|
|
}
|
|
|
|
static StringBuffer Acquire (int capacity) {
|
|
if (capacity <= MaxCachedSize) {
|
|
var buffer = CachedInstance;
|
|
if (buffer != null) {
|
|
CachedInstance = null;
|
|
buffer.Clear();
|
|
buffer.CheckCapacity(capacity);
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
return new StringBuffer(capacity);
|
|
}
|
|
|
|
static void Release (StringBuffer buffer) {
|
|
if (buffer.buffer.Length <= MaxCachedSize)
|
|
CachedInstance = buffer;
|
|
}
|
|
|
|
[ThreadStatic]
|
|
static StringBuffer CachedInstance;
|
|
|
|
static readonly CachedCulture CachedInvariantCulture = new CachedCulture(CultureInfo.InvariantCulture);
|
|
static readonly CachedCulture CachedCurrentCulture = new CachedCulture(CultureInfo.CurrentCulture);
|
|
|
|
const int DefaultCapacity = 32;
|
|
const int MaxCachedSize = 360; // same as BCL's StringBuilderCache
|
|
const int MaxArgs = 256;
|
|
const int MaxSpacing = 1000000;
|
|
const int MaxSpecifierSize = 32;
|
|
|
|
const string TrueLiteral = "True";
|
|
const string FalseLiteral = "False";
|
|
|
|
// The point of this class is to allow us to generate a direct call to a known
|
|
// method on an unknown, unconstrained generic value type. Normally this would
|
|
// be impossible; you'd have to cast the generic argument and introduce boxing.
|
|
// Instead we pay a one-time startup cost to create a delegate that will forward
|
|
// the parameter to the appropriate method in a strongly typed fashion.
|
|
static class ValueHelper<T> {
|
|
public static Action<StringBuffer, T, StringView> Formatter = Prepare();
|
|
|
|
static Action<StringBuffer, T, StringView> Prepare () {
|
|
// we only use this class for value types that also implement IStringFormattable
|
|
var type = typeof(T);
|
|
if (!typeof(IStringFormattable).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()))
|
|
return null;
|
|
|
|
var result = typeof(ValueHelper<T>)
|
|
.GetTypeInfo()
|
|
.GetDeclaredMethod("Assign")
|
|
.MakeGenericMethod(type)
|
|
.Invoke(null, null);
|
|
return (Action<StringBuffer, T, StringView>)result;
|
|
}
|
|
|
|
public static Action<StringBuffer, U, StringView> Assign<U>() where U : IStringFormattable {
|
|
return (f, u, v) => u.Format(f, v);
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: clean this up
|
|
public unsafe struct StringView {
|
|
public static readonly StringView Empty = new StringView();
|
|
|
|
public readonly char* Data;
|
|
public readonly int Length;
|
|
|
|
public bool IsEmpty {
|
|
get { return Length == 0; }
|
|
}
|
|
|
|
public StringView (char* data, int length) {
|
|
Data = data;
|
|
Length = length;
|
|
}
|
|
|
|
public static bool operator ==(StringView lhs, string rhs) {
|
|
var count = lhs.Length;
|
|
if (count != rhs.Length)
|
|
return false;
|
|
|
|
fixed (char* r = rhs)
|
|
{
|
|
var lhsPtr = lhs.Data;
|
|
var rhsPtr = r;
|
|
for (int i = 0; i < count; i++) {
|
|
if (*lhsPtr++ != *rhsPtr++)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static bool operator !=(StringView lhs, string rhs) {
|
|
return !(lhs == rhs);
|
|
}
|
|
}
|
|
|
|
// caches formatting information from culture data
|
|
// some of the accessors on NumberFormatInfo allocate copies of their data
|
|
sealed class CachedCulture {
|
|
public readonly CultureInfo Culture;
|
|
|
|
public readonly NumberFormatData CurrencyData;
|
|
public readonly NumberFormatData FixedData;
|
|
public readonly NumberFormatData NumberData;
|
|
public readonly NumberFormatData ScientificData;
|
|
public readonly NumberFormatData PercentData;
|
|
|
|
public readonly string CurrencyNegativePattern;
|
|
public readonly string CurrencyPositivePattern;
|
|
public readonly string CurrencySymbol;
|
|
|
|
public readonly string NumberNegativePattern;
|
|
public readonly string NumberPositivePattern;
|
|
|
|
public readonly string PercentNegativePattern;
|
|
public readonly string PercentPositivePattern;
|
|
public readonly string PercentSymbol;
|
|
public readonly string PerMilleSymbol;
|
|
|
|
public readonly string NegativeSign;
|
|
public readonly string PositiveSign;
|
|
|
|
public readonly string NaN;
|
|
public readonly string PositiveInfinity;
|
|
public readonly string NegativeInfinity;
|
|
|
|
public readonly int DecimalBufferSize;
|
|
|
|
public CachedCulture (CultureInfo culture) {
|
|
Culture = culture;
|
|
|
|
var info = culture.NumberFormat;
|
|
CurrencyData = new NumberFormatData(
|
|
info.CurrencyDecimalDigits,
|
|
info.NegativeSign,
|
|
info.CurrencyDecimalSeparator,
|
|
info.CurrencyGroupSeparator,
|
|
info.CurrencyGroupSizes,
|
|
info.CurrencySymbol.Length
|
|
);
|
|
|
|
FixedData = new NumberFormatData(
|
|
info.NumberDecimalDigits,
|
|
info.NegativeSign,
|
|
info.NumberDecimalSeparator,
|
|
null,
|
|
null,
|
|
0
|
|
);
|
|
|
|
NumberData = new NumberFormatData(
|
|
info.NumberDecimalDigits,
|
|
info.NegativeSign,
|
|
info.NumberDecimalSeparator,
|
|
info.NumberGroupSeparator,
|
|
info.NumberGroupSizes,
|
|
0
|
|
);
|
|
|
|
ScientificData = new NumberFormatData(
|
|
6,
|
|
info.NegativeSign,
|
|
info.NumberDecimalSeparator,
|
|
null,
|
|
null,
|
|
info.NegativeSign.Length + info.PositiveSign.Length * 2 // for number and exponent
|
|
);
|
|
|
|
PercentData = new NumberFormatData(
|
|
info.PercentDecimalDigits,
|
|
info.NegativeSign,
|
|
info.PercentDecimalSeparator,
|
|
info.PercentGroupSeparator,
|
|
info.PercentGroupSizes,
|
|
info.PercentSymbol.Length
|
|
);
|
|
|
|
CurrencyNegativePattern = NegativeCurrencyFormats[info.CurrencyNegativePattern];
|
|
CurrencyPositivePattern = PositiveCurrencyFormats[info.CurrencyPositivePattern];
|
|
CurrencySymbol = info.CurrencySymbol;
|
|
NumberNegativePattern = NegativeNumberFormats[info.NumberNegativePattern];
|
|
NumberPositivePattern = PositiveNumberFormat;
|
|
PercentNegativePattern = NegativePercentFormats[info.PercentNegativePattern];
|
|
PercentPositivePattern = PositivePercentFormats[info.PercentPositivePattern];
|
|
PercentSymbol = info.PercentSymbol;
|
|
PerMilleSymbol = info.PerMilleSymbol;
|
|
NegativeSign = info.NegativeSign;
|
|
PositiveSign = info.PositiveSign;
|
|
NaN = info.NaNSymbol;
|
|
PositiveInfinity = info.PositiveInfinitySymbol;
|
|
NegativeInfinity = info.NegativeInfinitySymbol;
|
|
DecimalBufferSize =
|
|
NumberFormatData.MinBufferSize +
|
|
info.NumberDecimalSeparator.Length +
|
|
(NegativeSign.Length + PositiveSign.Length) * 2;
|
|
}
|
|
|
|
static readonly string[] PositiveCurrencyFormats = {
|
|
"$#", "#$", "$ #", "# $"
|
|
};
|
|
|
|
static readonly string[] NegativeCurrencyFormats = {
|
|
"($#)", "-$#", "$-#", "$#-",
|
|
"(#$)", "-#$", "#-$", "#$-",
|
|
"-# $", "-$ #", "# $-", "$ #-",
|
|
"$ -#", "#- $", "($ #)", "(# $)"
|
|
};
|
|
|
|
static readonly string[] PositivePercentFormats = {
|
|
"# %", "#%", "%#", "% #"
|
|
};
|
|
|
|
static readonly string[] NegativePercentFormats = {
|
|
"-# %", "-#%", "-%#",
|
|
"%-#", "%#-",
|
|
"#-%", "#%-",
|
|
"-% #", "# %-", "% #-",
|
|
"% -#", "#- %"
|
|
};
|
|
|
|
static readonly string[] NegativeNumberFormats = {
|
|
"(#)", "-#", "- #", "#-", "# -",
|
|
};
|
|
|
|
static readonly string PositiveNumberFormat = "#";
|
|
}
|
|
|
|
// contains format information for a specific kind of format string
|
|
// e.g. (fixed, number, currency)
|
|
sealed class NumberFormatData {
|
|
readonly int bufferLength;
|
|
readonly int perDigitLength;
|
|
|
|
public readonly int DecimalDigits;
|
|
public readonly string NegativeSign;
|
|
public readonly string DecimalSeparator;
|
|
public readonly string GroupSeparator;
|
|
public readonly int[] GroupSizes;
|
|
|
|
public NumberFormatData (int decimalDigits, string negativeSign, string decimalSeparator, string groupSeparator, int[] groupSizes, int extra) {
|
|
DecimalDigits = decimalDigits;
|
|
NegativeSign = negativeSign;
|
|
DecimalSeparator = decimalSeparator;
|
|
GroupSeparator = groupSeparator;
|
|
GroupSizes = groupSizes;
|
|
|
|
bufferLength = MinBufferSize;
|
|
bufferLength += NegativeSign.Length;
|
|
bufferLength += DecimalSeparator.Length;
|
|
bufferLength += extra;
|
|
|
|
if (GroupSeparator != null)
|
|
perDigitLength = GroupSeparator.Length;
|
|
}
|
|
|
|
public int GetBufferSize (ref int maxDigits, int scale) {
|
|
if (maxDigits < 0)
|
|
maxDigits = DecimalDigits;
|
|
|
|
var digitCount = scale >= 0 ? scale + maxDigits : 0;
|
|
long len = bufferLength;
|
|
|
|
// calculate buffer size
|
|
len += digitCount;
|
|
len += perDigitLength * digitCount;
|
|
return checked((int)len);
|
|
}
|
|
|
|
internal const int MinBufferSize = 105;
|
|
}
|
|
|
|
// Most of the implementation of this file was ported from the native versions built into the CLR
|
|
// See: https://github.com/dotnet/coreclr/blob/838807429a0828a839958e3b7d392d65886c8f2e/src/classlibnative/bcltype/number.cpp
|
|
// Also see: https://github.com/dotnet/coreclr/blob/02084af832c2900cf6eac2a168c41f261409be97/src/mscorlib/src/System/Number.cs
|
|
// Standard numeric format string reference: https://msdn.microsoft.com/en-us/library/dwhawy9k%28v=vs.110%29.aspx
|
|
|
|
unsafe static partial class Numeric {
|
|
public static void FormatSByte (StringBuffer formatter, sbyte value, StringView specifier, CachedCulture culture) {
|
|
if (value < 0 && !specifier.IsEmpty) {
|
|
// if we're negative and doing a hex format, mask out the bits for the conversion
|
|
char c = specifier.Data[0];
|
|
if (c == 'X' || c == 'x') {
|
|
FormatUInt32(formatter, (uint)(value & 0xFF), specifier, culture);
|
|
return;
|
|
}
|
|
}
|
|
|
|
FormatInt32(formatter, value, specifier, culture);
|
|
}
|
|
|
|
public static void FormatInt16 (StringBuffer formatter, short value, StringView specifier, CachedCulture culture) {
|
|
if (value < 0 && !specifier.IsEmpty) {
|
|
// if we're negative and doing a hex format, mask out the bits for the conversion
|
|
char c = specifier.Data[0];
|
|
if (c == 'X' || c == 'x') {
|
|
FormatUInt32(formatter, (uint)(value & 0xFFFF), specifier, culture);
|
|
return;
|
|
}
|
|
}
|
|
|
|
FormatInt32(formatter, value, specifier, culture);
|
|
}
|
|
|
|
public static void FormatInt32 (StringBuffer formatter, int value, StringView specifier, CachedCulture culture) {
|
|
int digits;
|
|
var fmt = ParseFormatSpecifier(specifier, out digits);
|
|
|
|
// ANDing with 0xFFDF has the effect of uppercasing the character
|
|
switch (fmt & 0xFFDF) {
|
|
case 'G':
|
|
if (digits > 0)
|
|
goto default;
|
|
else
|
|
goto case 'D';
|
|
|
|
case 'D':
|
|
Int32ToDecStr(formatter, value, digits, culture.NegativeSign);
|
|
break;
|
|
|
|
case 'X':
|
|
// fmt-('X'-'A'+1) gives us the base hex character in either
|
|
// uppercase or lowercase, depending on the casing of fmt
|
|
Int32ToHexStr(formatter, (uint)value, fmt - ('X' - 'A' + 10), digits);
|
|
break;
|
|
|
|
default:
|
|
var number = new Number();
|
|
var buffer = stackalloc char[MaxNumberDigits + 1];
|
|
number.Digits = buffer;
|
|
Int32ToNumber(value, ref number);
|
|
if (fmt != 0)
|
|
NumberToString(formatter, ref number, fmt, digits, culture);
|
|
else
|
|
NumberToCustomFormatString(formatter, ref number, specifier, culture);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void FormatUInt32 (StringBuffer formatter, uint value, StringView specifier, CachedCulture culture) {
|
|
int digits;
|
|
var fmt = ParseFormatSpecifier(specifier, out digits);
|
|
|
|
// ANDing with 0xFFDF has the effect of uppercasing the character
|
|
switch (fmt & 0xFFDF) {
|
|
case 'G':
|
|
if (digits > 0)
|
|
goto default;
|
|
else
|
|
goto case 'D';
|
|
|
|
case 'D':
|
|
UInt32ToDecStr(formatter, value, digits);
|
|
break;
|
|
|
|
case 'X':
|
|
// fmt-('X'-'A'+1) gives us the base hex character in either
|
|
// uppercase or lowercase, depending on the casing of fmt
|
|
Int32ToHexStr(formatter, value, fmt - ('X' - 'A' + 10), digits);
|
|
break;
|
|
|
|
default:
|
|
var number = new Number();
|
|
var buffer = stackalloc char[MaxNumberDigits + 1];
|
|
number.Digits = buffer;
|
|
UInt32ToNumber(value, ref number);
|
|
if (fmt != 0)
|
|
NumberToString(formatter, ref number, fmt, digits, culture);
|
|
else
|
|
NumberToCustomFormatString(formatter, ref number, specifier, culture);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void FormatInt64 (StringBuffer formatter, long value, StringView specifier, CachedCulture culture) {
|
|
int digits;
|
|
var fmt = ParseFormatSpecifier(specifier, out digits);
|
|
|
|
// ANDing with 0xFFDF has the effect of uppercasing the character
|
|
switch (fmt & 0xFFDF) {
|
|
case 'G':
|
|
if (digits > 0)
|
|
goto default;
|
|
else
|
|
goto case 'D';
|
|
|
|
case 'D':
|
|
Int64ToDecStr(formatter, value, digits, culture.NegativeSign);
|
|
break;
|
|
|
|
case 'X':
|
|
// fmt-('X'-'A'+1) gives us the base hex character in either
|
|
// uppercase or lowercase, depending on the casing of fmt
|
|
Int64ToHexStr(formatter, (ulong)value, fmt - ('X' - 'A' + 10), digits);
|
|
break;
|
|
|
|
default:
|
|
var number = new Number();
|
|
var buffer = stackalloc char[MaxNumberDigits + 1];
|
|
number.Digits = buffer;
|
|
Int64ToNumber(value, ref number);
|
|
if (fmt != 0)
|
|
NumberToString(formatter, ref number, fmt, digits, culture);
|
|
else
|
|
NumberToCustomFormatString(formatter, ref number, specifier, culture);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void FormatUInt64 (StringBuffer formatter, ulong value, StringView specifier, CachedCulture culture) {
|
|
int digits;
|
|
var fmt = ParseFormatSpecifier(specifier, out digits);
|
|
|
|
// ANDing with 0xFFDF has the effect of uppercasing the character
|
|
switch (fmt & 0xFFDF) {
|
|
case 'G':
|
|
if (digits > 0)
|
|
goto default;
|
|
else
|
|
goto case 'D';
|
|
|
|
case 'D':
|
|
UInt64ToDecStr(formatter, value, digits);
|
|
break;
|
|
|
|
case 'X':
|
|
// fmt-('X'-'A'+1) gives us the base hex character in either
|
|
// uppercase or lowercase, depending on the casing of fmt
|
|
Int64ToHexStr(formatter, value, fmt - ('X' - 'A' + 10), digits);
|
|
break;
|
|
|
|
default:
|
|
var number = new Number();
|
|
var buffer = stackalloc char[MaxNumberDigits + 1];
|
|
number.Digits = buffer;
|
|
UInt64ToNumber(value, ref number);
|
|
if (fmt != 0)
|
|
NumberToString(formatter, ref number, fmt, digits, culture);
|
|
else
|
|
NumberToCustomFormatString(formatter, ref number, specifier, culture);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public static void FormatSingle (StringBuffer formatter, float value, StringView specifier, CachedCulture culture) {
|
|
int digits;
|
|
int precision = FloatPrecision;
|
|
var fmt = ParseFormatSpecifier(specifier, out digits);
|
|
|
|
// ANDing with 0xFFDF has the effect of uppercasing the character
|
|
switch (fmt & 0xFFDF) {
|
|
case 'G':
|
|
if (digits > 7)
|
|
precision = 9;
|
|
break;
|
|
|
|
case 'E':
|
|
if (digits > 6)
|
|
precision = 9;
|
|
break;
|
|
}
|
|
|
|
var number = new Number();
|
|
var buffer = stackalloc char[MaxFloatingDigits + 1];
|
|
number.Digits = buffer;
|
|
DoubleToNumber(value, precision, ref number);
|
|
|
|
if (number.Scale == ScaleNaN) {
|
|
formatter.Append(culture.NaN);
|
|
return;
|
|
}
|
|
|
|
if (number.Scale == ScaleInf) {
|
|
if (number.Sign > 0)
|
|
formatter.Append(culture.NegativeInfinity);
|
|
else
|
|
formatter.Append(culture.PositiveInfinity);
|
|
return;
|
|
}
|
|
|
|
if (fmt != 0)
|
|
NumberToString(formatter, ref number, fmt, digits, culture);
|
|
else
|
|
NumberToCustomFormatString(formatter, ref number, specifier, culture);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void FormatDouble (StringBuffer formatter, double value, StringView specifier, CachedCulture culture) {
|
|
int digits;
|
|
int precision = DoublePrecision;
|
|
var fmt = ParseFormatSpecifier(specifier, out digits);
|
|
|
|
// ANDing with 0xFFDF has the effect of uppercasing the character
|
|
switch (fmt & 0xFFDF) {
|
|
case 'G':
|
|
if (digits > 15)
|
|
precision = 17;
|
|
break;
|
|
|
|
case 'E':
|
|
if (digits > 14)
|
|
precision = 17;
|
|
break;
|
|
}
|
|
|
|
var number = new Number();
|
|
var buffer = stackalloc char[MaxFloatingDigits + 1];
|
|
number.Digits = buffer;
|
|
DoubleToNumber(value, precision, ref number);
|
|
|
|
if (number.Scale == ScaleNaN) {
|
|
formatter.Append(culture.NaN);
|
|
return;
|
|
}
|
|
|
|
if (number.Scale == ScaleInf) {
|
|
if (number.Sign > 0)
|
|
formatter.Append(culture.NegativeInfinity);
|
|
else
|
|
formatter.Append(culture.PositiveInfinity);
|
|
return;
|
|
}
|
|
|
|
if (fmt != 0)
|
|
NumberToString(formatter, ref number, fmt, digits, culture);
|
|
else
|
|
NumberToCustomFormatString(formatter, ref number, specifier, culture);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public static void FormatDecimal (StringBuffer formatter, uint* value, StringView specifier, CachedCulture culture) {
|
|
int digits;
|
|
var fmt = ParseFormatSpecifier(specifier, out digits);
|
|
|
|
var number = new Number();
|
|
var buffer = stackalloc char[MaxNumberDigits + 1];
|
|
number.Digits = buffer;
|
|
DecimalToNumber(value, ref number);
|
|
if (fmt != 0)
|
|
NumberToString(formatter, ref number, fmt, digits, culture, isDecimal: true);
|
|
else
|
|
NumberToCustomFormatString(formatter, ref number, specifier, culture);
|
|
}
|
|
|
|
static void NumberToString (StringBuffer formatter, ref Number number, char format, int maxDigits, CachedCulture culture, bool isDecimal = false) {
|
|
// ANDing with 0xFFDF has the effect of uppercasing the character
|
|
switch (format & 0xFFDF) {
|
|
case 'C':
|
|
{
|
|
var cultureData = culture.CurrencyData;
|
|
var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale);
|
|
RoundNumber(ref number, number.Scale + maxDigits);
|
|
|
|
var buffer = stackalloc char[bufferSize];
|
|
var ptr = FormatCurrency(
|
|
buffer,
|
|
ref number,
|
|
maxDigits,
|
|
cultureData,
|
|
number.Sign > 0 ? culture.CurrencyNegativePattern : culture.CurrencyPositivePattern,
|
|
culture.CurrencySymbol
|
|
);
|
|
|
|
formatter.Append(buffer, (int)(ptr - buffer));
|
|
break;
|
|
}
|
|
|
|
case 'F':
|
|
{
|
|
var cultureData = culture.FixedData;
|
|
var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale);
|
|
RoundNumber(ref number, number.Scale + maxDigits);
|
|
|
|
var buffer = stackalloc char[bufferSize];
|
|
var ptr = buffer;
|
|
if (number.Sign > 0)
|
|
AppendString(&ptr, cultureData.NegativeSign);
|
|
|
|
ptr = FormatFixed(ptr, ref number, maxDigits, cultureData);
|
|
formatter.Append(buffer, (int)(ptr - buffer));
|
|
break;
|
|
}
|
|
|
|
case 'N':
|
|
{
|
|
var cultureData = culture.NumberData;
|
|
var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale);
|
|
RoundNumber(ref number, number.Scale + maxDigits);
|
|
|
|
var buffer = stackalloc char[bufferSize];
|
|
var ptr = FormatNumber(
|
|
buffer,
|
|
ref number,
|
|
maxDigits,
|
|
number.Sign > 0 ? culture.NumberNegativePattern : culture.NumberPositivePattern,
|
|
cultureData
|
|
);
|
|
|
|
formatter.Append(buffer, (int)(ptr - buffer));
|
|
break;
|
|
}
|
|
|
|
case 'E':
|
|
{
|
|
var cultureData = culture.ScientificData;
|
|
var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale);
|
|
maxDigits++;
|
|
|
|
RoundNumber(ref number, maxDigits);
|
|
|
|
var buffer = stackalloc char[bufferSize];
|
|
var ptr = buffer;
|
|
if (number.Sign > 0)
|
|
AppendString(&ptr, cultureData.NegativeSign);
|
|
|
|
ptr = FormatScientific(
|
|
ptr,
|
|
ref number,
|
|
maxDigits,
|
|
format, // TODO: fix casing
|
|
cultureData.DecimalSeparator,
|
|
culture.PositiveSign,
|
|
culture.NegativeSign
|
|
);
|
|
|
|
formatter.Append(buffer, (int)(ptr - buffer));
|
|
break;
|
|
}
|
|
|
|
case 'P':
|
|
{
|
|
number.Scale += 2;
|
|
var cultureData = culture.PercentData;
|
|
var bufferSize = cultureData.GetBufferSize(ref maxDigits, number.Scale);
|
|
RoundNumber(ref number, number.Scale + maxDigits);
|
|
|
|
var buffer = stackalloc char[bufferSize];
|
|
var ptr = FormatPercent(
|
|
buffer,
|
|
ref number,
|
|
maxDigits,
|
|
cultureData,
|
|
number.Sign > 0 ? culture.PercentNegativePattern : culture.PercentPositivePattern,
|
|
culture.PercentSymbol
|
|
);
|
|
|
|
formatter.Append(buffer, (int)(ptr - buffer));
|
|
break;
|
|
}
|
|
|
|
case 'G':
|
|
{
|
|
var enableRounding = true;
|
|
if (maxDigits < 1) {
|
|
if (isDecimal && maxDigits == -1) {
|
|
// if we're formatting a decimal, default to 29 digits precision
|
|
// only for G formatting without a precision specifier
|
|
maxDigits = DecimalPrecision;
|
|
enableRounding = false;
|
|
}
|
|
else
|
|
maxDigits = number.Precision;
|
|
}
|
|
|
|
var bufferSize = maxDigits + culture.DecimalBufferSize;
|
|
var buffer = stackalloc char[bufferSize];
|
|
var ptr = buffer;
|
|
|
|
// round for G formatting only if a precision is given
|
|
// we need to handle the minus zero case also
|
|
if (enableRounding)
|
|
RoundNumber(ref number, maxDigits);
|
|
else if (isDecimal && number.Digits[0] == 0)
|
|
number.Sign = 0;
|
|
|
|
if (number.Sign > 0)
|
|
AppendString(&ptr, culture.NegativeSign);
|
|
|
|
ptr = FormatGeneral(
|
|
ptr,
|
|
ref number,
|
|
maxDigits,
|
|
(char)(format - ('G' - 'E')),
|
|
culture.NumberData.DecimalSeparator,
|
|
culture.PositiveSign,
|
|
culture.NegativeSign,
|
|
!enableRounding
|
|
);
|
|
|
|
formatter.Append(buffer, (int)(ptr - buffer));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new FormatException(string.Format(SR.UnknownFormatSpecifier, format));
|
|
}
|
|
}
|
|
|
|
static char* FormatCurrency (char* buffer, ref Number number, int maxDigits, NumberFormatData data, string currencyFormat, string currencySymbol) {
|
|
for (int i = 0; i < currencyFormat.Length; i++) {
|
|
char c = currencyFormat[i];
|
|
switch (c) {
|
|
case '#': buffer = FormatFixed(buffer, ref number, maxDigits, data); break;
|
|
case '-': AppendString(&buffer, data.NegativeSign); break;
|
|
case '$': AppendString(&buffer, currencySymbol); break;
|
|
default: *buffer++ = c; break;
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static char* FormatNumber (char* buffer, ref Number number, int maxDigits, string format, NumberFormatData data) {
|
|
for (int i = 0; i < format.Length; i++) {
|
|
char c = format[i];
|
|
switch (c) {
|
|
case '#': buffer = FormatFixed(buffer, ref number, maxDigits, data); break;
|
|
case '-': AppendString(&buffer, data.NegativeSign); break;
|
|
default: *buffer++ = c; break;
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static char* FormatPercent (char* buffer, ref Number number, int maxDigits, NumberFormatData data, string format, string percentSymbol) {
|
|
for (int i = 0; i < format.Length; i++) {
|
|
char c = format[i];
|
|
switch (c) {
|
|
case '#': buffer = FormatFixed(buffer, ref number, maxDigits, data); break;
|
|
case '-': AppendString(&buffer, data.NegativeSign); break;
|
|
case '%': AppendString(&buffer, percentSymbol); break;
|
|
default: *buffer++ = c; break;
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static char* FormatGeneral (
|
|
char* buffer, ref Number number, int maxDigits, char expChar,
|
|
string decimalSeparator, string positiveSign, string negativeSign,
|
|
bool suppressScientific) {
|
|
|
|
var digitPos = number.Scale;
|
|
var scientific = false;
|
|
if (!suppressScientific) {
|
|
if (digitPos > maxDigits || digitPos < -3) {
|
|
digitPos = 1;
|
|
scientific = true;
|
|
}
|
|
}
|
|
|
|
var digits = number.Digits;
|
|
if (digitPos <= 0)
|
|
*buffer++ = '0';
|
|
else {
|
|
do {
|
|
*buffer++ = *digits != 0 ? *digits++ : '0';
|
|
} while (--digitPos > 0);
|
|
}
|
|
|
|
if (*digits != 0 || digitPos < 0) {
|
|
AppendString(&buffer, decimalSeparator);
|
|
while (digitPos < 0) {
|
|
*buffer++ = '0';
|
|
digitPos++;
|
|
}
|
|
|
|
while (*digits != 0)
|
|
*buffer++ = *digits++;
|
|
}
|
|
|
|
if (scientific)
|
|
buffer = FormatExponent(buffer, number.Scale - 1, expChar, positiveSign, negativeSign, 2);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static char* FormatScientific (
|
|
char* buffer, ref Number number, int maxDigits, char expChar,
|
|
string decimalSeparator, string positiveSign, string negativeSign) {
|
|
|
|
var digits = number.Digits;
|
|
*buffer++ = *digits != 0 ? *digits++ : '0';
|
|
if (maxDigits != 1)
|
|
AppendString(&buffer, decimalSeparator);
|
|
|
|
while (--maxDigits > 0)
|
|
*buffer++ = *digits != 0 ? *digits++ : '0';
|
|
|
|
int e = number.Digits[0] == 0 ? 0 : number.Scale - 1;
|
|
return FormatExponent(buffer, e, expChar, positiveSign, negativeSign, 3);
|
|
}
|
|
|
|
static char* FormatExponent (char* buffer, int value, char expChar, string positiveSign, string negativeSign, int minDigits) {
|
|
*buffer++ = expChar;
|
|
if (value < 0) {
|
|
AppendString(&buffer, negativeSign);
|
|
value = -value;
|
|
}
|
|
else if (positiveSign != null)
|
|
AppendString(&buffer, positiveSign);
|
|
|
|
var digits = stackalloc char[11];
|
|
var ptr = Int32ToDecChars(digits + 10, (uint)value, minDigits);
|
|
var len = (int)(digits + 10 - ptr);
|
|
while (--len >= 0)
|
|
*buffer++ = *ptr++;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static char* FormatFixed (char* buffer, ref Number number, int maxDigits, NumberFormatData data) {
|
|
var groups = data.GroupSizes;
|
|
var digits = number.Digits;
|
|
var digitPos = number.Scale;
|
|
if (digitPos <= 0)
|
|
*buffer++ = '0';
|
|
else if (groups != null) {
|
|
var groupIndex = 0;
|
|
var groupSizeCount = groups[0];
|
|
var groupSizeLen = groups.Length;
|
|
var newBufferSize = digitPos;
|
|
var groupSeparatorLen = data.GroupSeparator.Length;
|
|
var groupSize = 0;
|
|
|
|
// figure out the size of the result
|
|
if (groupSizeLen != 0) {
|
|
while (digitPos > groupSizeCount) {
|
|
groupSize = groups[groupIndex];
|
|
if (groupSize == 0)
|
|
break;
|
|
|
|
newBufferSize += groupSeparatorLen;
|
|
if (groupIndex < groupSizeLen - 1)
|
|
groupIndex++;
|
|
|
|
groupSizeCount += groups[groupIndex];
|
|
if (groupSizeCount < 0 || newBufferSize < 0)
|
|
throw new ArgumentOutOfRangeException(SR.InvalidGroupSizes);
|
|
}
|
|
|
|
if (groupSizeCount == 0)
|
|
groupSize = 0;
|
|
else
|
|
groupSize = groups[0];
|
|
}
|
|
|
|
groupIndex = 0;
|
|
var digitCount = 0;
|
|
var digitLength = StrLen(digits);
|
|
var digitStart = digitPos < digitLength ? digitPos : digitLength;
|
|
var ptr = buffer + newBufferSize - 1;
|
|
|
|
for (int i = digitPos - 1; i >= 0; i--) {
|
|
*(ptr--) = i < digitStart ? digits[i] : '0';
|
|
|
|
// check if we need to add a group separator
|
|
if (groupSize > 0) {
|
|
digitCount++;
|
|
if (digitCount == groupSize && i != 0) {
|
|
for (int j = groupSeparatorLen - 1; j >= 0; j--)
|
|
*(ptr--) = data.GroupSeparator[j];
|
|
|
|
if (groupIndex < groupSizeLen - 1) {
|
|
groupIndex++;
|
|
groupSize = groups[groupIndex];
|
|
}
|
|
digitCount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
buffer += newBufferSize;
|
|
digits += digitStart;
|
|
}
|
|
else {
|
|
do {
|
|
*buffer++ = *digits != 0 ? *digits++ : '0';
|
|
}
|
|
while (--digitPos > 0);
|
|
}
|
|
|
|
if (maxDigits > 0) {
|
|
AppendString(&buffer, data.DecimalSeparator);
|
|
while (digitPos < 0 && maxDigits > 0) {
|
|
*buffer++ = '0';
|
|
digitPos++;
|
|
maxDigits--;
|
|
}
|
|
|
|
while (maxDigits > 0) {
|
|
*buffer++ = *digits != 0 ? *digits++ : '0';
|
|
maxDigits--;
|
|
}
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
static void Int32ToDecStr (StringBuffer formatter, int value, int digits, string negativeSign) {
|
|
if (digits < 1)
|
|
digits = 1;
|
|
|
|
var maxDigits = digits > 15 ? digits : 15;
|
|
var bufferLength = maxDigits > 100 ? maxDigits : 100;
|
|
var negativeLength = 0;
|
|
|
|
if (value < 0) {
|
|
negativeLength = negativeSign.Length;
|
|
if (negativeLength > bufferLength - maxDigits)
|
|
bufferLength = negativeLength + maxDigits;
|
|
}
|
|
|
|
var buffer = stackalloc char[bufferLength];
|
|
var p = Int32ToDecChars(buffer + bufferLength, value >= 0 ? (uint)value : (uint)-value, digits);
|
|
if (value < 0) {
|
|
// add the negative sign
|
|
for (int i = negativeLength - 1; i >= 0; i--)
|
|
*(--p) = negativeSign[i];
|
|
}
|
|
|
|
formatter.Append(p, (int)(buffer + bufferLength - p));
|
|
}
|
|
|
|
static void UInt32ToDecStr (StringBuffer formatter, uint value, int digits) {
|
|
var buffer = stackalloc char[100];
|
|
if (digits < 1)
|
|
digits = 1;
|
|
|
|
var p = Int32ToDecChars(buffer + 100, value, digits);
|
|
formatter.Append(p, (int)(buffer + 100 - p));
|
|
}
|
|
|
|
static void Int32ToHexStr (StringBuffer formatter, uint value, int hexBase, int digits) {
|
|
var buffer = stackalloc char[100];
|
|
if (digits < 1)
|
|
digits = 1;
|
|
|
|
var p = Int32ToHexChars(buffer + 100, value, hexBase, digits);
|
|
formatter.Append(p, (int)(buffer + 100 - p));
|
|
}
|
|
|
|
static void Int64ToDecStr (StringBuffer formatter, long value, int digits, string negativeSign) {
|
|
if (digits < 1)
|
|
digits = 1;
|
|
|
|
var sign = (int)High32((ulong)value);
|
|
var maxDigits = digits > 20 ? digits : 20;
|
|
var bufferLength = maxDigits > 100 ? maxDigits : 100;
|
|
|
|
if (sign < 0) {
|
|
value = -value;
|
|
var negativeLength = negativeSign.Length;
|
|
if (negativeLength > bufferLength - maxDigits)
|
|
bufferLength = negativeLength + maxDigits;
|
|
}
|
|
|
|
var buffer = stackalloc char[bufferLength];
|
|
var p = buffer + bufferLength;
|
|
var uv = (ulong)value;
|
|
while (High32(uv) != 0) {
|
|
p = Int32ToDecChars(p, Int64DivMod(ref uv), 9);
|
|
digits -= 9;
|
|
}
|
|
|
|
p = Int32ToDecChars(p, Low32(uv), digits);
|
|
if (sign < 0) {
|
|
// add the negative sign
|
|
for (int i = negativeSign.Length - 1; i >= 0; i--)
|
|
*(--p) = negativeSign[i];
|
|
}
|
|
|
|
formatter.Append(p, (int)(buffer + bufferLength - p));
|
|
}
|
|
|
|
static void UInt64ToDecStr (StringBuffer formatter, ulong value, int digits) {
|
|
if (digits < 1)
|
|
digits = 1;
|
|
|
|
var buffer = stackalloc char[100];
|
|
var p = buffer + 100;
|
|
while (High32(value) != 0) {
|
|
p = Int32ToDecChars(p, Int64DivMod(ref value), 9);
|
|
digits -= 9;
|
|
}
|
|
|
|
p = Int32ToDecChars(p, Low32(value), digits);
|
|
formatter.Append(p, (int)(buffer + 100 - p));
|
|
}
|
|
|
|
static void Int64ToHexStr (StringBuffer formatter, ulong value, int hexBase, int digits) {
|
|
var buffer = stackalloc char[100];
|
|
char* ptr;
|
|
if (High32(value) != 0) {
|
|
Int32ToHexChars(buffer + 100, Low32(value), hexBase, 8);
|
|
ptr = Int32ToHexChars(buffer + 100 - 8, High32(value), hexBase, digits - 8);
|
|
}
|
|
else {
|
|
if (digits < 1)
|
|
digits = 1;
|
|
ptr = Int32ToHexChars(buffer + 100, Low32(value), hexBase, digits);
|
|
}
|
|
|
|
formatter.Append(ptr, (int)(buffer + 100 - ptr));
|
|
}
|
|
|
|
static char* Int32ToDecChars (char* p, uint value, int digits) {
|
|
while (value != 0) {
|
|
*--p = (char)(value % 10 + '0');
|
|
value /= 10;
|
|
digits--;
|
|
}
|
|
|
|
while (--digits >= 0)
|
|
*--p = '0';
|
|
return p;
|
|
}
|
|
|
|
static char* Int32ToHexChars (char* p, uint value, int hexBase, int digits) {
|
|
while (--digits >= 0 || value != 0) {
|
|
var digit = value & 0xF;
|
|
*--p = (char)(digit + (digit < 10 ? '0' : hexBase));
|
|
value >>= 4;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static char ParseFormatSpecifier (StringView specifier, out int digits) {
|
|
if (specifier.IsEmpty) {
|
|
digits = -1;
|
|
return 'G';
|
|
}
|
|
|
|
char* curr = specifier.Data;
|
|
char first = *curr++;
|
|
if ((first >= 'A' && first <= 'Z') || (first >= 'a' && first <= 'z')) {
|
|
int n = -1;
|
|
char c = *curr++;
|
|
if (c >= '0' && c <= '9') {
|
|
n = c - '0';
|
|
c = *curr++;
|
|
while (c >= '0' && c <= '9') {
|
|
n = n * 10 + c - '0';
|
|
c = *curr++;
|
|
if (n >= 10)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (c == 0) {
|
|
digits = n;
|
|
return first;
|
|
}
|
|
}
|
|
|
|
digits = -1;
|
|
return (char)0;
|
|
}
|
|
|
|
static void Int32ToNumber (int value, ref Number number) {
|
|
number.Precision = Int32Precision;
|
|
if (value >= 0)
|
|
number.Sign = 0;
|
|
else {
|
|
number.Sign = 1;
|
|
value = -value;
|
|
}
|
|
|
|
var buffer = stackalloc char[Int32Precision + 1];
|
|
var ptr = Int32ToDecChars(buffer + Int32Precision, (uint)value, 0);
|
|
var len = (int)(buffer + Int32Precision - ptr);
|
|
number.Scale = len;
|
|
|
|
var dest = number.Digits;
|
|
while (--len >= 0)
|
|
*dest++ = *ptr++;
|
|
*dest = '\0';
|
|
}
|
|
|
|
static void UInt32ToNumber (uint value, ref Number number) {
|
|
number.Precision = UInt32Precision;
|
|
number.Sign = 0;
|
|
|
|
var buffer = stackalloc char[UInt32Precision + 1];
|
|
var ptr = Int32ToDecChars(buffer + UInt32Precision, value, 0);
|
|
var len = (int)(buffer + UInt32Precision - ptr);
|
|
number.Scale = len;
|
|
|
|
var dest = number.Digits;
|
|
while (--len >= 0)
|
|
*dest++ = *ptr++;
|
|
*dest = '\0';
|
|
}
|
|
|
|
static void Int64ToNumber (long value, ref Number number) {
|
|
number.Precision = Int64Precision;
|
|
if (value >= 0)
|
|
number.Sign = 0;
|
|
else {
|
|
number.Sign = 1;
|
|
value = -value;
|
|
}
|
|
|
|
var buffer = stackalloc char[Int64Precision + 1];
|
|
var ptr = buffer + Int64Precision;
|
|
var uv = (ulong)value;
|
|
while (High32(uv) != 0)
|
|
ptr = Int32ToDecChars(ptr, Int64DivMod(ref uv), 9);
|
|
|
|
ptr = Int32ToDecChars(ptr, Low32(uv), 0);
|
|
var len = (int)(buffer + Int64Precision - ptr);
|
|
number.Scale = len;
|
|
|
|
var dest = number.Digits;
|
|
while (--len >= 0)
|
|
*dest++ = *ptr++;
|
|
*dest = '\0';
|
|
}
|
|
|
|
static void UInt64ToNumber (ulong value, ref Number number) {
|
|
number.Precision = UInt64Precision;
|
|
number.Sign = 0;
|
|
|
|
var buffer = stackalloc char[UInt64Precision + 1];
|
|
var ptr = buffer + UInt64Precision;
|
|
while (High32(value) != 0)
|
|
ptr = Int32ToDecChars(ptr, Int64DivMod(ref value), 9);
|
|
|
|
ptr = Int32ToDecChars(ptr, Low32(value), 0);
|
|
|
|
var len = (int)(buffer + UInt64Precision - ptr);
|
|
number.Scale = len;
|
|
|
|
var dest = number.Digits;
|
|
while (--len >= 0)
|
|
*dest++ = *ptr++;
|
|
*dest = '\0';
|
|
}
|
|
|
|
static void DoubleToNumber (double value, int precision, ref Number number) {
|
|
number.Precision = precision;
|
|
|
|
uint sign, exp, mantHi, mantLo;
|
|
ExplodeDouble(value, out sign, out exp, out mantHi, out mantLo);
|
|
|
|
if (exp == 0x7FF) {
|
|
// special value handling (infinity and NaNs)
|
|
number.Scale = (mantLo != 0 || mantHi != 0) ? ScaleNaN : ScaleInf;
|
|
number.Sign = (int)sign;
|
|
number.Digits[0] = '\0';
|
|
}
|
|
else {
|
|
// convert the digits of the number to characters
|
|
if (value < 0) {
|
|
number.Sign = 1;
|
|
value = -value;
|
|
}
|
|
|
|
var digits = number.Digits;
|
|
var end = digits + MaxFloatingDigits;
|
|
var p = end;
|
|
var shift = 0;
|
|
double intPart;
|
|
double reducedInt;
|
|
var fracPart = ModF(value, out intPart);
|
|
|
|
if (intPart != 0) {
|
|
// format the integer part
|
|
while (intPart != 0) {
|
|
reducedInt = ModF(intPart / 10, out intPart);
|
|
*--p = (char)((int)((reducedInt + 0.03) * 10) + '0');
|
|
shift++;
|
|
}
|
|
while (p < end)
|
|
*digits++ = *p++;
|
|
}
|
|
else if (fracPart > 0) {
|
|
// normalize the fractional part
|
|
while ((reducedInt = fracPart * 10) < 1) {
|
|
fracPart = reducedInt;
|
|
shift--;
|
|
}
|
|
}
|
|
|
|
// concat the fractional part, padding the remainder with zeros
|
|
p = number.Digits + precision;
|
|
while (digits <= p && digits < end) {
|
|
fracPart *= 10;
|
|
fracPart = ModF(fracPart, out reducedInt);
|
|
*digits++ = (char)((int)reducedInt + '0');
|
|
}
|
|
|
|
// round the result if necessary
|
|
digits = p;
|
|
*p = (char)(*p + 5);
|
|
while (*p > '9') {
|
|
*p = '0';
|
|
if (p > number.Digits)
|
|
++*--p;
|
|
else {
|
|
*p = '1';
|
|
shift++;
|
|
}
|
|
}
|
|
|
|
number.Scale = shift;
|
|
*digits = '\0';
|
|
}
|
|
}
|
|
|
|
static void DecimalToNumber (uint* value, ref Number number) {
|
|
// bit 31 of the decimal is the sign bit
|
|
// bits 16-23 contain the scale
|
|
number.Sign = (int)(*value >> 31);
|
|
number.Scale = (int)((*value >> 16) & 0xFF);
|
|
number.Precision = DecimalPrecision;
|
|
|
|
// loop for as long as the decimal is larger than 32 bits
|
|
var buffer = stackalloc char[DecimalPrecision + 1];
|
|
var p = buffer + DecimalPrecision;
|
|
var hi = *(value + 1);
|
|
var lo = *(value + 2);
|
|
var mid = *(value + 3);
|
|
|
|
while ((mid | hi) != 0) {
|
|
// keep dividing down by one billion at a time
|
|
ulong n = hi;
|
|
hi = (uint)(n / OneBillion);
|
|
n = (n % OneBillion) << 32 | mid;
|
|
mid = (uint)(n / OneBillion);
|
|
n = (n % OneBillion) << 32 | lo;
|
|
lo = (uint)(n / OneBillion);
|
|
|
|
// format this portion of the number
|
|
p = Int32ToDecChars(p, (uint)(n % OneBillion), 9);
|
|
}
|
|
|
|
// finish off with the low 32-bits of the decimal, if anything is left over
|
|
p = Int32ToDecChars(p, lo, 0);
|
|
|
|
var len = (int)(buffer + DecimalPrecision - p);
|
|
number.Scale = len - number.Scale;
|
|
|
|
var dest = number.Digits;
|
|
while (--len >= 0)
|
|
*dest++ = *p++;
|
|
*dest = '\0';
|
|
}
|
|
|
|
static void RoundNumber (ref Number number, int pos) {
|
|
var digits = number.Digits;
|
|
int i = 0;
|
|
while (i < pos && digits[i] != 0) i++;
|
|
if (i == pos && digits[i] >= '5') {
|
|
while (i > 0 && digits[i - 1] == '9') i--;
|
|
if (i > 0)
|
|
digits[i - 1]++;
|
|
else {
|
|
number.Scale++;
|
|
digits[0] = '1';
|
|
i = 1;
|
|
}
|
|
}
|
|
else {
|
|
while (i > 0 && digits[i - 1] == '0')
|
|
i--;
|
|
}
|
|
|
|
if (i == 0) {
|
|
number.Scale = 0;
|
|
number.Sign = 0;
|
|
}
|
|
|
|
digits[i] = '\0';
|
|
}
|
|
|
|
static void AppendString (char** buffer, string value) {
|
|
fixed (char* pinnedString = value)
|
|
{
|
|
var length = value.Length;
|
|
for (var src = pinnedString; src < pinnedString + length; (*buffer)++, src++)
|
|
**buffer = *src;
|
|
}
|
|
}
|
|
|
|
static int StrLen (char* str) {
|
|
int count = 0;
|
|
while (*str++ != 0)
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
static uint Int64DivMod (ref ulong value) {
|
|
var rem = (uint)(value % 1000000000);
|
|
value /= 1000000000;
|
|
return rem;
|
|
}
|
|
|
|
static double ModF (double value, out double intPart) {
|
|
intPart = Math.Truncate(value);
|
|
return value - intPart;
|
|
}
|
|
|
|
static void ExplodeDouble (double value, out uint sign, out uint exp, out uint mantHi, out uint mantLo) {
|
|
var bits = *(ulong*)&value;
|
|
if (BitConverter.IsLittleEndian) {
|
|
mantLo = (uint)(bits & 0xFFFFFFFF); // bits 0 - 31
|
|
mantHi = (uint)((bits >> 32) & 0xFFFFF); // bits 32 - 51
|
|
exp = (uint)((bits >> 52) & 0x7FF); // bits 52 - 62
|
|
sign = (uint)((bits >> 63) & 0x1); // bit 63
|
|
}
|
|
else {
|
|
sign = (uint)(bits & 0x1); // bit 0
|
|
exp = (uint)((bits >> 1) & 0x7FF); // bits 1 - 11
|
|
mantHi = (uint)((bits >> 12) & 0xFFFFF); // bits 12 - 31
|
|
mantLo = (uint)(bits >> 32); // bits 32 - 63
|
|
}
|
|
}
|
|
|
|
static uint Low32 (ulong value) {
|
|
return (uint)value;
|
|
}
|
|
|
|
static uint High32 (ulong value) {
|
|
return (uint)((value & 0xFFFFFFFF00000000) >> 32);
|
|
}
|
|
|
|
struct Number {
|
|
public int Precision;
|
|
public int Scale;
|
|
public int Sign;
|
|
public char* Digits;
|
|
|
|
// useful for debugging
|
|
public override string ToString () {
|
|
return new string(Digits);
|
|
}
|
|
}
|
|
|
|
const int MaxNumberDigits = 50;
|
|
const int MaxFloatingDigits = 352;
|
|
const int Int32Precision = 10;
|
|
const int UInt32Precision = 10;
|
|
const int Int64Precision = 19;
|
|
const int UInt64Precision = 20;
|
|
const int FloatPrecision = 7;
|
|
const int DoublePrecision = 15;
|
|
const int DecimalPrecision = 29;
|
|
const int ScaleNaN = unchecked((int)0x80000000);
|
|
const int ScaleInf = 0x7FFFFFFF;
|
|
const int OneBillion = 1000000000;
|
|
}
|
|
|
|
// currently just contains some hardcoded exception messages
|
|
static class SR {
|
|
public const string InvalidGroupSizes = "Invalid group sizes in NumberFormatInfo.";
|
|
public const string UnknownFormatSpecifier = "Unknown format specifier '{0}'.";
|
|
public const string ArgIndexOutOfRange = "No format argument exists for index '{0}'.";
|
|
public const string TypeNotFormattable = "Type '{0}' is not a built-in type, does not implement IStringFormattable, and no custom formatter was found for it.";
|
|
public const string InvalidFormatString = "Invalid format string.";
|
|
}
|
|
|
|
/// <summary>
|
|
/// A low-allocation version of the built-in <see cref="StringBuilder"/> type.
|
|
/// </summary>
|
|
partial class StringBuffer {
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
public void AppendFormat<T0>(string format, T0 arg0) {
|
|
var args = new Arg1<T0>(arg0);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
public static string Format<T0>(string format, T0 arg0) {
|
|
var buffer = Acquire(format.Length + 8);
|
|
buffer.AppendFormat(format, arg0);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
public void AppendFormat<T0, T1>(string format, T0 arg0, T1 arg1) {
|
|
var args = new Arg2<T0, T1>(arg0, arg1);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
public static string Format<T0, T1>(string format, T0 arg0, T1 arg1) {
|
|
var buffer = Acquire(format.Length + 16);
|
|
buffer.AppendFormat(format, arg0, arg1);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
public void AppendFormat<T0, T1, T2>(string format, T0 arg0, T1 arg1, T2 arg2) {
|
|
var args = new Arg3<T0, T1, T2>(arg0, arg1, arg2);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
public static string Format<T0, T1, T2>(string format, T0 arg0, T1 arg1, T2 arg2) {
|
|
var buffer = Acquire(format.Length + 24);
|
|
buffer.AppendFormat(format, arg0, arg1, arg2);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
public void AppendFormat<T0, T1, T2, T3>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
|
|
var args = new Arg4<T0, T1, T2, T3>(arg0, arg1, arg2, arg3);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
public static string Format<T0, T1, T2, T3>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
|
|
var buffer = Acquire(format.Length + 32);
|
|
buffer.AppendFormat(format, arg0, arg1, arg2, arg3);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
public void AppendFormat<T0, T1, T2, T3, T4>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
|
|
var args = new Arg5<T0, T1, T2, T3, T4>(arg0, arg1, arg2, arg3, arg4);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
public static string Format<T0, T1, T2, T3, T4>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
|
|
var buffer = Acquire(format.Length + 40);
|
|
buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
/// <param name="arg5">A value to format.</param>
|
|
public void AppendFormat<T0, T1, T2, T3, T4, T5>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) {
|
|
var args = new Arg6<T0, T1, T2, T3, T4, T5>(arg0, arg1, arg2, arg3, arg4, arg5);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
/// <param name="arg5">A value to format.</param>
|
|
public static string Format<T0, T1, T2, T3, T4, T5>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) {
|
|
var buffer = Acquire(format.Length + 48);
|
|
buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4, arg5);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
/// <param name="arg5">A value to format.</param>
|
|
/// <param name="arg6">A value to format.</param>
|
|
public void AppendFormat<T0, T1, T2, T3, T4, T5, T6>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) {
|
|
var args = new Arg7<T0, T1, T2, T3, T4, T5, T6>(arg0, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
/// <param name="arg5">A value to format.</param>
|
|
/// <param name="arg6">A value to format.</param>
|
|
public static string Format<T0, T1, T2, T3, T4, T5, T6>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) {
|
|
var buffer = Acquire(format.Length + 56);
|
|
buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends the string returned by processing a composite format string, which contains zero or more format items, to this instance. Each format item is replaced by the string representation of a single argument.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
/// <param name="arg5">A value to format.</param>
|
|
/// <param name="arg6">A value to format.</param>
|
|
/// <param name="arg7">A value to format.</param>
|
|
public void AppendFormat<T0, T1, T2, T3, T4, T5, T6, T7>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) {
|
|
var args = new Arg8<T0, T1, T2, T3, T4, T5, T6, T7>(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
|
AppendArgSet(format, ref args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts the value of objects to strings based on the formats specified and inserts them into another string.
|
|
/// </summary>
|
|
/// <param name="format">A composite format string.</param>
|
|
/// <param name="arg0">A value to format.</param>
|
|
/// <param name="arg1">A value to format.</param>
|
|
/// <param name="arg2">A value to format.</param>
|
|
/// <param name="arg3">A value to format.</param>
|
|
/// <param name="arg4">A value to format.</param>
|
|
/// <param name="arg5">A value to format.</param>
|
|
/// <param name="arg6">A value to format.</param>
|
|
/// <param name="arg7">A value to format.</param>
|
|
public static string Format<T0, T1, T2, T3, T4, T5, T6, T7>(string format, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) {
|
|
var buffer = Acquire(format.Length + 64);
|
|
buffer.AppendFormat(format, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
|
var result = buffer.ToString();
|
|
Release(buffer);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg1<T0> : IArgSet {
|
|
T0 t0;
|
|
|
|
public int Count => 1;
|
|
|
|
public Arg1 (T0 t0) {
|
|
this.t0 = t0;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg2<T0, T1> : IArgSet {
|
|
T0 t0;
|
|
T1 t1;
|
|
|
|
public int Count => 2;
|
|
|
|
public Arg2 (T0 t0, T1 t1) {
|
|
this.t0 = t0;
|
|
this.t1 = t1;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
case 1: buffer.AppendGeneric(t1, format); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg3<T0, T1, T2> : IArgSet {
|
|
T0 t0;
|
|
T1 t1;
|
|
T2 t2;
|
|
|
|
public int Count => 3;
|
|
|
|
public Arg3 (T0 t0, T1 t1, T2 t2) {
|
|
this.t0 = t0;
|
|
this.t1 = t1;
|
|
this.t2 = t2;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
case 1: buffer.AppendGeneric(t1, format); break;
|
|
case 2: buffer.AppendGeneric(t2, format); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg4<T0, T1, T2, T3> : IArgSet {
|
|
T0 t0;
|
|
T1 t1;
|
|
T2 t2;
|
|
T3 t3;
|
|
|
|
public int Count => 4;
|
|
|
|
public Arg4 (T0 t0, T1 t1, T2 t2, T3 t3) {
|
|
this.t0 = t0;
|
|
this.t1 = t1;
|
|
this.t2 = t2;
|
|
this.t3 = t3;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
case 1: buffer.AppendGeneric(t1, format); break;
|
|
case 2: buffer.AppendGeneric(t2, format); break;
|
|
case 3: buffer.AppendGeneric(t3, format); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg5<T0, T1, T2, T3, T4> : IArgSet {
|
|
T0 t0;
|
|
T1 t1;
|
|
T2 t2;
|
|
T3 t3;
|
|
T4 t4;
|
|
|
|
public int Count => 5;
|
|
|
|
public Arg5 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4) {
|
|
this.t0 = t0;
|
|
this.t1 = t1;
|
|
this.t2 = t2;
|
|
this.t3 = t3;
|
|
this.t4 = t4;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
case 1: buffer.AppendGeneric(t1, format); break;
|
|
case 2: buffer.AppendGeneric(t2, format); break;
|
|
case 3: buffer.AppendGeneric(t3, format); break;
|
|
case 4: buffer.AppendGeneric(t4, format); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg6<T0, T1, T2, T3, T4, T5> : IArgSet {
|
|
T0 t0;
|
|
T1 t1;
|
|
T2 t2;
|
|
T3 t3;
|
|
T4 t4;
|
|
T5 t5;
|
|
|
|
public int Count => 6;
|
|
|
|
public Arg6 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) {
|
|
this.t0 = t0;
|
|
this.t1 = t1;
|
|
this.t2 = t2;
|
|
this.t3 = t3;
|
|
this.t4 = t4;
|
|
this.t5 = t5;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
case 1: buffer.AppendGeneric(t1, format); break;
|
|
case 2: buffer.AppendGeneric(t2, format); break;
|
|
case 3: buffer.AppendGeneric(t3, format); break;
|
|
case 4: buffer.AppendGeneric(t4, format); break;
|
|
case 5: buffer.AppendGeneric(t5, format); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg7<T0, T1, T2, T3, T4, T5, T6> : IArgSet {
|
|
T0 t0;
|
|
T1 t1;
|
|
T2 t2;
|
|
T3 t3;
|
|
T4 t4;
|
|
T5 t5;
|
|
T6 t6;
|
|
|
|
public int Count => 7;
|
|
|
|
public Arg7 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) {
|
|
this.t0 = t0;
|
|
this.t1 = t1;
|
|
this.t2 = t2;
|
|
this.t3 = t3;
|
|
this.t4 = t4;
|
|
this.t5 = t5;
|
|
this.t6 = t6;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
case 1: buffer.AppendGeneric(t1, format); break;
|
|
case 2: buffer.AppendGeneric(t2, format); break;
|
|
case 3: buffer.AppendGeneric(t3, format); break;
|
|
case 4: buffer.AppendGeneric(t4, format); break;
|
|
case 5: buffer.AppendGeneric(t5, format); break;
|
|
case 6: buffer.AppendGeneric(t6, format); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe struct Arg8<T0, T1, T2, T3, T4, T5, T6, T7> : IArgSet {
|
|
T0 t0;
|
|
T1 t1;
|
|
T2 t2;
|
|
T3 t3;
|
|
T4 t4;
|
|
T5 t5;
|
|
T6 t6;
|
|
T7 t7;
|
|
|
|
public int Count => 8;
|
|
|
|
public Arg8 (T0 t0, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) {
|
|
this.t0 = t0;
|
|
this.t1 = t1;
|
|
this.t2 = t2;
|
|
this.t3 = t3;
|
|
this.t4 = t4;
|
|
this.t5 = t5;
|
|
this.t6 = t6;
|
|
this.t7 = t7;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Format (StringBuffer buffer, int index, StringView format) {
|
|
switch (index) {
|
|
case 0: buffer.AppendGeneric(t0, format); break;
|
|
case 1: buffer.AppendGeneric(t1, format); break;
|
|
case 2: buffer.AppendGeneric(t2, format); break;
|
|
case 3: buffer.AppendGeneric(t3, format); break;
|
|
case 4: buffer.AppendGeneric(t4, format); break;
|
|
case 5: buffer.AppendGeneric(t5, format); break;
|
|
case 6: buffer.AppendGeneric(t6, format); break;
|
|
case 7: buffer.AppendGeneric(t7, format); break;
|
|
}
|
|
}
|
|
}
|
|
}
|