230 lines
10 KiB
C#
230 lines
10 KiB
C#
|
using System;
|
||
|
using System.Runtime.CompilerServices;
|
||
|
|
||
|
namespace Unity.Collections
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Provides methods for parsing numbers from FixedString*N*Bytes.
|
||
|
/// </summary>
|
||
|
[GenerateTestsForBurstCompatibility]
|
||
|
public unsafe static partial class FixedStringMethods
|
||
|
{
|
||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
|
[GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
|
||
|
internal static bool ParseLongInternal<T>(ref T fs, ref int offset, out long value)
|
||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||
|
{
|
||
|
int resetOffset = offset;
|
||
|
int sign = 1;
|
||
|
if (offset < fs.Length)
|
||
|
{
|
||
|
if (fs.Peek(offset).value == '+')
|
||
|
fs.Read(ref offset);
|
||
|
else if (fs.Peek(offset).value == '-')
|
||
|
{
|
||
|
sign = -1;
|
||
|
fs.Read(ref offset);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int digitOffset = offset;
|
||
|
value = 0;
|
||
|
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
|
||
|
{
|
||
|
value *= 10;
|
||
|
value += fs.Read(ref offset).value - '0';
|
||
|
}
|
||
|
value = sign * value;
|
||
|
|
||
|
// If there was no number parsed, revert the offset since it's a syntax error and we might
|
||
|
// have erroneously parsed a '-' or '+'
|
||
|
if (offset == digitOffset)
|
||
|
{
|
||
|
offset = resetOffset;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Parses an int from this string starting at a byte offset.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Stops parsing after the last number character. (Unlike parsing methods in other API's, this method does not expect to necessarily parse the entire string.)
|
||
|
///
|
||
|
/// The parsed value is bitwise-identical to the result of System.Int32.Parse.
|
||
|
/// </remarks>
|
||
|
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
|
||
|
/// <param name="fs">The string from which to parse.</param>
|
||
|
/// <param name="offset">A reference to an index of the byte at which to parse an int.</param>
|
||
|
/// <param name="output">Outputs the parsed int. Ignore if parsing fails.</param>
|
||
|
/// <returns>ParseError.None if successful. Otherwise returns ParseError.Overflow or ParseError.Syntax.</returns>
|
||
|
[GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
|
||
|
public static ParseError Parse<T>(ref this T fs, ref int offset, ref int output)
|
||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||
|
{
|
||
|
if (!ParseLongInternal(ref fs, ref offset, out long value))
|
||
|
return ParseError.Syntax;
|
||
|
if (value > int.MaxValue)
|
||
|
return ParseError.Overflow;
|
||
|
if (value < int.MinValue)
|
||
|
return ParseError.Overflow;
|
||
|
output = (int)value;
|
||
|
return ParseError.None;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Parses an uint from this string starting at a byte offset.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Stops parsing after the last number character. (Unlike parsing methods in other API's, this method does not expect to necessarily parse the entire string.)
|
||
|
///
|
||
|
/// The parsed value is bitwise-identical to the result of System.UInt32.Parse.
|
||
|
/// </remarks>
|
||
|
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
|
||
|
/// <param name="fs">The string from which to parse.</param>
|
||
|
/// <param name="offset">A reference to an index of the byte at which to parse a uint.</param>
|
||
|
/// <param name="output">Outputs the parsed uint. Ignore if parsing fails.</param>
|
||
|
/// <returns>ParseError.None if successful. Otherwise returns ParseError.Overflow or ParseError.Syntax.</returns>
|
||
|
[GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
|
||
|
public static ParseError Parse<T>(ref this T fs, ref int offset, ref uint output)
|
||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||
|
{
|
||
|
if (!ParseLongInternal(ref fs, ref offset, out long value))
|
||
|
return ParseError.Syntax;
|
||
|
if (value > uint.MaxValue)
|
||
|
return ParseError.Overflow;
|
||
|
if (value < uint.MinValue)
|
||
|
return ParseError.Overflow;
|
||
|
output = (uint)value;
|
||
|
return ParseError.None;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Parses a float from this string starting at a byte offset.
|
||
|
/// </summary>
|
||
|
/// Stops parsing after the last number character. (Unlike parsing methods in other API's, this method does not expect to necessarily parse the entire string.)
|
||
|
///
|
||
|
/// <remarks>The parsed value is bitwise-identical to the result of System.Single.Parse.</remarks>
|
||
|
/// <typeparam name="T">A FixedString*N*Bytes type.</typeparam>
|
||
|
/// <param name="fs">The string from which to parse.</param>
|
||
|
/// <param name="offset">Index of the byte at which to parse a float.</param>
|
||
|
/// <param name="output">Outputs the parsed float. Ignore if parsing fails.</param>
|
||
|
/// <param name="decimalSeparator">The character used to separate the integer part of the number from the fractional part. Defaults to '.' (period).</param>
|
||
|
/// <returns>ParseError.None if successful. Otherwise returns ParseError.Overflow, ParseError.Underflow, or ParseError.Syntax.</returns>
|
||
|
[GenerateTestsForBurstCompatibility(GenericTypeArguments = new[] { typeof(FixedString128Bytes) })]
|
||
|
public static ParseError Parse<T>(ref this T fs, ref int offset, ref float output, char decimalSeparator = '.')
|
||
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||
|
{
|
||
|
int resetOffset = offset;
|
||
|
int sign = 1;
|
||
|
if (offset < fs.Length)
|
||
|
{
|
||
|
if (fs.Peek(offset).value == '+')
|
||
|
fs.Read(ref offset);
|
||
|
else if (fs.Peek(offset).value == '-')
|
||
|
{
|
||
|
sign = -1;
|
||
|
fs.Read(ref offset);
|
||
|
}
|
||
|
}
|
||
|
if (fs.Found(ref offset, 'n', 'a', 'n'))
|
||
|
{
|
||
|
FixedStringUtils.UintFloatUnion ufu = new FixedStringUtils.UintFloatUnion();
|
||
|
ufu.uintValue = 4290772992U;
|
||
|
output = ufu.floatValue;
|
||
|
return ParseError.None;
|
||
|
}
|
||
|
if (fs.Found(ref offset, 'i', 'n', 'f', 'i', 'n', 'i', 't', 'y'))
|
||
|
{
|
||
|
output = (sign == 1) ? Single.PositiveInfinity : Single.NegativeInfinity;
|
||
|
return ParseError.None;
|
||
|
}
|
||
|
|
||
|
ulong decimalMantissa = 0;
|
||
|
int significantDigits = 0;
|
||
|
int digitsAfterDot = 0;
|
||
|
int mantissaDigits = 0;
|
||
|
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
|
||
|
{
|
||
|
++mantissaDigits;
|
||
|
if (significantDigits < 9)
|
||
|
{
|
||
|
var temp = decimalMantissa * 10 + (ulong)(fs.Peek(offset).value - '0');
|
||
|
if (temp > decimalMantissa)
|
||
|
++significantDigits;
|
||
|
decimalMantissa = temp;
|
||
|
}
|
||
|
else
|
||
|
--digitsAfterDot;
|
||
|
fs.Read(ref offset);
|
||
|
}
|
||
|
if (offset < fs.Length && fs.Peek(offset).value == decimalSeparator)
|
||
|
{
|
||
|
fs.Read(ref offset);
|
||
|
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
|
||
|
{
|
||
|
++mantissaDigits;
|
||
|
if (significantDigits < 9)
|
||
|
{
|
||
|
var temp = decimalMantissa * 10 + (ulong)(fs.Peek(offset).value - '0');
|
||
|
if (temp > decimalMantissa)
|
||
|
++significantDigits;
|
||
|
decimalMantissa = temp;
|
||
|
++digitsAfterDot;
|
||
|
}
|
||
|
fs.Read(ref offset);
|
||
|
}
|
||
|
}
|
||
|
if (mantissaDigits == 0)
|
||
|
{
|
||
|
// Reset offset in case '+' or '-' was erroneously parsed
|
||
|
offset = resetOffset;
|
||
|
return ParseError.Syntax;
|
||
|
}
|
||
|
int decimalExponent = 0;
|
||
|
int decimalExponentSign = 1;
|
||
|
if (offset < fs.Length && (fs.Peek(offset).value | 32) == 'e')
|
||
|
{
|
||
|
fs.Read(ref offset);
|
||
|
if (offset < fs.Length)
|
||
|
{
|
||
|
if (fs.Peek(offset).value == '+')
|
||
|
fs.Read(ref offset);
|
||
|
else if (fs.Peek(offset).value == '-')
|
||
|
{
|
||
|
decimalExponentSign = -1;
|
||
|
fs.Read(ref offset);
|
||
|
}
|
||
|
}
|
||
|
int digitOffset = offset;
|
||
|
while (offset < fs.Length && Unicode.Rune.IsDigit(fs.Peek(offset)))
|
||
|
{
|
||
|
decimalExponent = decimalExponent * 10 + (fs.Peek(offset).value - '0');
|
||
|
fs.Read(ref offset);
|
||
|
}
|
||
|
if (offset == digitOffset)
|
||
|
{
|
||
|
// Reset offset in case '+' or '-' was erroneously parsed
|
||
|
offset = resetOffset;
|
||
|
return ParseError.Syntax;
|
||
|
}
|
||
|
if (decimalExponent > 38)
|
||
|
{
|
||
|
if (decimalExponentSign == 1)
|
||
|
return ParseError.Overflow;
|
||
|
else
|
||
|
return ParseError.Underflow;
|
||
|
}
|
||
|
}
|
||
|
decimalExponent = decimalExponent * decimalExponentSign - digitsAfterDot;
|
||
|
var error = FixedStringUtils.Base10ToBase2(ref output, decimalMantissa, decimalExponent);
|
||
|
if (error != ParseError.None)
|
||
|
return error;
|
||
|
output *= sign;
|
||
|
return ParseError.None;
|
||
|
}
|
||
|
}
|
||
|
}
|