Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/libraries/Common/src/System/Number.Parsing.Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace System
{
internal static partial class Number
{
private static unsafe bool TryParseNumber<TChar>(scoped ref TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info)
private static unsafe bool TryParseNumber<TChar>(TChar* str, TChar* strEnd, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, out int elementsConsumed)
where TChar : unmanaged, IUtfChar<TChar>
{
Debug.Assert(str != null);
Expand Down Expand Up @@ -269,33 +269,37 @@ private static unsafe bool TryParseNumber<TChar>(scoped ref TChar* str, TChar* s
number.IsNegative = false;
}
}
str = p;
return true;

int index = (int)(p - str);
int length = (int)(strEnd - str);

if ((index == length) || ((styles & NumberStyles.AllowTrailingInvalidCharacters) != 0) || TrailingZeros(new ReadOnlySpan<TChar>(str, length), index))
{
// For AllowTrailingInvalidCharacters, we want to stop at the first invalid character
// including trailing nulls (zeros). Otherwise, for compatibility we still need to
// process any trailing nulls that exist and report them as having been consumed.

elementsConsumed = index;
return true;
}
}
}
str = p;

elementsConsumed = 0;
return false;
}

internal static unsafe bool TryStringToNumber<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info)
internal static unsafe bool TryStringToNumber<TChar>(ReadOnlySpan<TChar> value, NumberStyles styles, ref NumberBuffer number, NumberFormatInfo info, out int elementsConsumed)
where TChar : unmanaged, IUtfChar<TChar>
{
Debug.Assert(info != null);

fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value))
{
TChar* p = stringPointer;

if (!TryParseNumber(ref p, p + value.Length, styles, ref number, info)
|| ((int)(p - stringPointer) < value.Length && !TrailingZeros(value, (int)(p - stringPointer))))
{
number.CheckConsistency();
return false;
}
bool succeeded = TryParseNumber(stringPointer, stringPointer + value.Length, styles, ref number, info, out elementsConsumed);
number.CheckConsistency();
return succeeded;
}

number.CheckConsistency();
return true;
}

[MethodImpl(MethodImplOptions.NoInlining)] // rare slow path that shouldn't impact perf of the main use case
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@
<value>Invalid handle.</value>
</data>
<data name="Arg_InvalidHexBinaryStyle" xml:space="preserve">
<value>With the AllowHexSpecifier or AllowBinarySpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be AllowLeadingWhite and AllowTrailingWhite.</value>
<value>With the AllowHexSpecifier or AllowBinarySpecifier bit set in the enum bit field, the only other valid bits that can be combined into the enum value must be AllowLeadingWhite, AllowTrailingWhite, and AllowTrailingInvalidCharacters.</value>
</data>
<data name="Arg_InvalidNeutralResourcesLanguage_Asm_Culture" xml:space="preserve">
<value>The NeutralResourcesLanguageAttribute on the assembly "{0}" specifies an invalid culture name: "{1}".</value>
Expand Down Expand Up @@ -4441,4 +4441,4 @@
<data name="IO_SharedMemory_DirectoryOwnerPermissionsIncorrect" xml:space="preserve">
<value>The directory '{0}' does not have the expected owner permissions: {1}.</value>
</data>
</root>
</root>
34 changes: 31 additions & 3 deletions src/libraries/System.Private.CoreLib/src/System/Byte.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I
result = 0;
return false;
}
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out byte result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public override string ToString()
Expand Down Expand Up @@ -1103,6 +1103,34 @@ static bool INumberBase<byte>.TryConvertToTruncating<TOther>(byte value, [MaybeN
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleInteger(style);

if (s is null)
{
result = 0;
charsConsumed = 0;
return false;
}
return Number.TryParseBinaryInteger(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out byte result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out byte result, out int bytesConsumed)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK;
}

//
// IParsable
//
Expand Down Expand Up @@ -1175,7 +1203,7 @@ public static byte Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Numbe
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out byte result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseBinaryInteger(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
38 changes: 38 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Char.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1972,6 +1972,44 @@ static bool INumberBase<char>.TryConvertToTruncating<TOther>(char value, [MaybeN

static bool INumberBase<char>.TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out char result) => TryParse(s, out result);

static bool INumberBase<char>.TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out char result, out int charsConsumed)
{
if (TryParse(s, out result))
{
charsConsumed = 1;
return true;
}

charsConsumed = 0;
return false;
}

static bool INumberBase<char>.TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out char result, out int charsConsumed)
{
if (TryParse(s, out result))
{
charsConsumed = 1;
return true;
}

charsConsumed = 0;
return false;
}

static bool INumberBase<char>.TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out char result, out int bytesConsumed)
{
if (Rune.DecodeFromUtf8(utf8Text, out Rune rune, out bytesConsumed) != Buffers.OperationStatus.Done ||
bytesConsumed != utf8Text.Length ||
!rune.IsBmp)
{
result = '\0';
return false;
}

result = (char)rune.Value;
return true;
}

//
// IParsable
//
Expand Down
34 changes: 31 additions & 3 deletions src/libraries/System.Private.CoreLib/src/System/Decimal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -557,13 +557,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I
result = 0;
return false;
}
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out decimal result)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

// Returns a binary representation of a Decimal. The return value is an
Expand Down Expand Up @@ -1803,6 +1803,34 @@ private static bool TryConvertTo<TOther>(decimal value, [MaybeNullWhen(false)] o
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);

if (s is null)
{
result = 0;
charsConsumed = 0;
return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these separate checks for null input? s.AsSpan will work on null, TryParse will fail on empty input, and null is not something we need to optimize for... right?

return Number.TryParseDecimal(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out decimal result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseDecimal(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result, out int bytesConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed) == Number.ParsingStatus.OK;
}

//
// IParsable
//
Expand Down Expand Up @@ -1842,7 +1870,7 @@ public static decimal Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Nu
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out decimal result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
return Number.TryParseDecimal(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _) == Number.ParsingStatus.OK;
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
34 changes: 31 additions & 3 deletions src/libraries/System.Private.CoreLib/src/System/Double.cs
Original file line number Diff line number Diff line change
Expand Up @@ -433,13 +433,13 @@ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, I
result = 0;
return false;
}
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result);
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out _);
}

public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out double result)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result);
return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out _);
}

//
Expand Down Expand Up @@ -1467,6 +1467,34 @@ private static bool TryConvertTo<TOther>(double value, [MaybeNullWhen(false)] ou
}
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(string, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);

if (s is null)
{
result = 0;
charsConsumed = 0;
return false;
}
return Number.TryParseFloat(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed);
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{char}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, IFormatProvider? provider, out double result, out int charsConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(s, style, NumberFormatInfo.GetInstance(provider), out result, out charsConsumed);
}

/// <inheritdoc cref="INumberBase{TSelf}.TryParse(ReadOnlySpan{byte}, NumberStyles, IFormatProvider?, out TSelf, out int)" />
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out double result, out int bytesConsumed)
{
NumberFormatInfo.ValidateParseStyleFloatingPoint(style);
return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out bytesConsumed);
}

//
// IParsable
//
Expand Down Expand Up @@ -2272,7 +2300,7 @@ public static double Parse(ReadOnlySpan<byte> utf8Text, NumberStyles style = Num
public static bool TryParse(ReadOnlySpan<byte> utf8Text, NumberStyles style, IFormatProvider? provider, out double result)
{
NumberFormatInfo.ValidateParseStyleInteger(style);
return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result);
return Number.TryParseFloat(utf8Text, style, NumberFormatInfo.GetInstance(provider), out result, out _);
}

/// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Private.CoreLib/src/System/Enum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ private static bool TryParseByValueOrName<TUnderlying, TStorage>(
NumberFormatInfo numberFormat = NumberFormatInfo.InvariantInfo;
const NumberStyles NumberStyle = NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingWhite;

Number.ParsingStatus status = Number.TryParseBinaryIntegerStyle(value, NumberStyle, numberFormat, out result);
Number.ParsingStatus status = Number.TryParseBinaryIntegerStyle(value, NumberStyle, numberFormat, out result, out _);
if (status == Number.ParsingStatus.OK)
{
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -798,21 +798,21 @@ public static NumberFormatInfo ReadOnly(NumberFormatInfo nfi)
return info;
}

// private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFFC00);
// private const NumberStyles InvalidNumberStyles = unchecked((NumberStyles) 0xFFFFF000);
private const NumberStyles InvalidNumberStyles = ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
| NumberStyles.AllowLeadingSign | NumberStyles.AllowTrailingSign
| NumberStyles.AllowParentheses | NumberStyles.AllowDecimalPoint
| NumberStyles.AllowThousands | NumberStyles.AllowExponent
| NumberStyles.AllowCurrencySymbol | NumberStyles.AllowHexSpecifier
| NumberStyles.AllowBinarySpecifier);
| NumberStyles.AllowBinarySpecifier | NumberStyles.AllowTrailingInvalidCharacters);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ValidateParseStyleInteger(NumberStyles style)
{
// Check for undefined flags or using AllowHexSpecifier/AllowBinarySpecifier each with anything other than AllowLeadingWhite/AllowTrailingWhite.
// Check for undefined flags or using AllowHexSpecifier/AllowBinarySpecifier each with anything other than AllowLeadingWhite/AllowTrailingWhite/AllowTrailingInvalidCharacters.
if ((style & (InvalidNumberStyles | NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) != 0 &&
(style & ~NumberStyles.HexNumber) != 0 &&
(style & ~NumberStyles.BinaryNumber) != 0)
(style & ~(NumberStyles.HexNumber | NumberStyles.AllowTrailingInvalidCharacters)) != 0 &&
(style & ~(NumberStyles.BinaryNumber | NumberStyles.AllowTrailingInvalidCharacters)) != 0)
{
ThrowInvalid(style);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public enum NumberStyles
/// </summary>
AllowBinarySpecifier = 0x00000400,

/// <summary>Indicates that the numeric string is allowed to contain otherwise invalid characters and that parsing should treat such characters as terminating the input.</summary>
AllowTrailingInvalidCharacters = 0x00000800,

Integer = AllowLeadingWhite | AllowTrailingWhite | AllowLeadingSign,

HexNumber = AllowLeadingWhite | AllowTrailingWhite | AllowHexSpecifier,
Expand Down
Loading
Loading