Skip to content
Open
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
144 changes: 67 additions & 77 deletions src/libraries/Common/src/System/IO/StringParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,147 +134,137 @@ public string ExtractCurrent()
}

/// <summary>Moves to the next component and parses it as an Int32.</summary>
public unsafe int ParseNextInt32()
public int ParseNextInt32()
{
MoveNextOrFail();

int startIndex = _startIndex;
int endIndex = _endIndex;
ReadOnlySpan<char> span = _buffer.AsSpan(startIndex, endIndex - startIndex);

if (span.IsEmpty)
{
ThrowForInvalidData();
}
Comment on lines +141 to +148

bool negative = false;
int result = 0;
int i = 0;

fixed (char* bufferPtr = _buffer)
if (span[0] == '-')
{
char* p = bufferPtr + _startIndex;
char* end = bufferPtr + _endIndex;

if (p == end)
negative = true;
i = 1;
if (i == span.Length)
{
ThrowForInvalidData();
}
}

if (*p == '-')
{
negative = true;
p++;
if (p == end)
{
ThrowForInvalidData();
}
}

while (p != end)
for (; i < span.Length; i++)
{
int d = span[i] - '0';
if (d < 0 || d > 9)
{
int d = *p - '0';
if (d < 0 || d > 9)
{
ThrowForInvalidData();
}
result = negative ? checked((result * 10) - d) : checked((result * 10) + d);

p++;
ThrowForInvalidData();
}
result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
}

Debug.Assert(result == int.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
return result;
}

/// <summary>Moves to the next component and parses it as an Int64.</summary>
public unsafe long ParseNextInt64()
public long ParseNextInt64()
Copy link
Member

Choose a reason for hiding this comment

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

Do we know why this is its own custom handler and not just reusing the shared number parsing code used by int/long?

Copy link
Member

@stephentoub stephentoub Mar 17, 2026

Choose a reason for hiding this comment

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

When this was written, span didn't exist yet, and we didn't want to allocate strings for each component just to be able to parse them. Now that span exists and you can int/long.Parse from them, that should be doable.

{
MoveNextOrFail();

int startIndex = _startIndex;
int endIndex = _endIndex;
ReadOnlySpan<char> span = _buffer.AsSpan(startIndex, endIndex - startIndex);

if (span.IsEmpty)
{
ThrowForInvalidData();
}

bool negative = false;
long result = 0;
int i = 0;

fixed (char* bufferPtr = _buffer)
if (span[0] == '-')
{
char* p = bufferPtr + _startIndex;
char* end = bufferPtr + _endIndex;

if (p == end)
negative = true;
i = 1;
if (i == span.Length)
{
ThrowForInvalidData();
}
}

if (*p == '-')
{
negative = true;
p++;
if (p == end)
{
ThrowForInvalidData();
}
}

while (p != end)
for (; i < span.Length; i++)
{
int d = span[i] - '0';
if (d < 0 || d > 9)
{
int d = *p - '0';
if (d < 0 || d > 9)
{
ThrowForInvalidData();
}
result = negative ? checked((result * 10) - d) : checked((result * 10) + d);

p++;
ThrowForInvalidData();
}
result = negative ? checked((result * 10) - d) : checked((result * 10) + d);
}

Debug.Assert(result == long.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
return result;
}

/// <summary>Moves to the next component and parses it as a UInt32.</summary>
public unsafe uint ParseNextUInt32()
public uint ParseNextUInt32()
{
MoveNextOrFail();
if (_startIndex == _endIndex)

ReadOnlySpan<char> span = _buffer.AsSpan(_startIndex, _endIndex - _startIndex);

if (span.IsEmpty)
{
ThrowForInvalidData();
}

uint result = 0;
fixed (char* bufferPtr = _buffer)
for (int i = 0; i < span.Length; i++)
{
char* p = bufferPtr + _startIndex;
char* end = bufferPtr + _endIndex;
while (p != end)
int d = span[i] - '0';
if (d < 0 || d > 9)
{
int d = *p - '0';
if (d < 0 || d > 9)
{
ThrowForInvalidData();
}
result = (uint)checked((result * 10) + d);

p++;
ThrowForInvalidData();
}
result = (uint)checked((result * 10) + d);
}

Debug.Assert(result == uint.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
return result;
}

/// <summary>Moves to the next component and parses it as a UInt64.</summary>
public unsafe ulong ParseNextUInt64()
public ulong ParseNextUInt64()
{
MoveNextOrFail();

ReadOnlySpan<char> span = _buffer.AsSpan(_startIndex, _endIndex - _startIndex);

if (span.IsEmpty)
{
ThrowForInvalidData();
}

ulong result = 0;
fixed (char* bufferPtr = _buffer)
for (int i = 0; i < span.Length; i++)
{
char* p = bufferPtr + _startIndex;
char* end = bufferPtr + _endIndex;
while (p != end)
int d = span[i] - '0';
if (d < 0 || d > 9)
{
int d = *p - '0';
if (d < 0 || d > 9)
{
ThrowForInvalidData();
}
result = checked((result * 10ul) + (ulong)d);

p++;
ThrowForInvalidData();
}
result = checked((result * 10ul) + (ulong)d);
}

Debug.Assert(result == ulong.Parse(ExtractCurrent()), "Expected manually parsed result to match Parse result");
Expand Down
Loading