| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446 |
- using System.Buffers;
- using System.Text;
- namespace Lua.Internal;
- sealed class Utf8Reader
- {
- [ThreadStatic]
- static byte[]? scratchBuffer;
- [ThreadStatic]
- internal static bool scratchBufferUsed;
- readonly byte[] buffer;
- int bufPos, bufLen;
- Decoder? decoder;
- const int ThreadStaticBufferSize = 1024;
- public Utf8Reader()
- {
- if (scratchBufferUsed)
- {
- buffer = new byte[ThreadStaticBufferSize];
- return;
- }
- scratchBuffer ??= new byte[ThreadStaticBufferSize];
- buffer = scratchBuffer;
- scratchBufferUsed = true;
- }
- public long Remain => bufLen - bufPos;
- public string? ReadLine(Stream stream, bool keepEol = false)
- {
- var resultBuffer = ArrayPool<byte>.Shared.Rent(1024);
- var lineLen = 0;
- try
- {
- while (true)
- {
- if (bufPos >= bufLen)
- {
- bufLen = stream.Read(buffer, 0, buffer.Length);
- bufPos = 0;
- if (bufLen == 0)
- {
- break; // EOF
- }
- }
- Span<byte> span = new(buffer, bufPos, bufLen - bufPos);
- var idx = span.IndexOfAny((byte)'\r', (byte)'\n');
- if (idx >= 0)
- {
- // Add the line content (before the newline)
- AppendToBuffer(ref resultBuffer, span[..idx], ref lineLen);
- var nl = span[idx];
- var eolStart = bufPos + idx;
- bufPos += idx + 1;
- // Handle CRLF - check if we have \r\n
- var isCRLF = false;
- if (nl == (byte)'\r' && bufPos < bufLen && buffer[bufPos] == (byte)'\n')
- {
- isCRLF = true;
- bufPos++; // Skip the \n as well
- }
- // Add end-of-line characters if keepEol is true
- if (keepEol)
- {
- if (isCRLF)
- {
- // Add \r\n
- AppendToBuffer(ref resultBuffer, new(new byte[] { (byte)'\r', (byte)'\n' }), ref lineLen);
- }
- else
- {
- // Add just the single newline character (\r or \n)
- AppendToBuffer(ref resultBuffer, new(new byte[] { nl }), ref lineLen);
- }
- }
- return Encoding.UTF8.GetString(resultBuffer, 0, lineLen);
- }
- else
- {
- // No newline found → add all to line buffer
- AppendToBuffer(ref resultBuffer, span, ref lineLen);
- bufPos = bufLen;
- }
- }
- if (lineLen == 0)
- {
- return null;
- }
- return Encoding.UTF8.GetString(resultBuffer, 0, lineLen);
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(resultBuffer);
- }
- }
- public string ReadToEnd(Stream stream)
- {
- var resultBuffer = ArrayPool<byte>.Shared.Rent(1024);
- var len = 0;
- try
- {
- while (true)
- {
- if (bufPos >= bufLen)
- {
- bufLen = stream.Read(buffer, 0, buffer.Length);
- bufPos = 0;
- if (bufLen == 0)
- {
- break; // EOF
- }
- }
- Span<byte> span = new(buffer, bufPos, bufLen - bufPos);
- AppendToBuffer(ref resultBuffer, span, ref len);
- bufPos = bufLen;
- }
- if (len == 0)
- {
- return "";
- }
- return Encoding.UTF8.GetString(resultBuffer, 0, len);
- }
- finally
- {
- ArrayPool<byte>.Shared.Return(resultBuffer);
- }
- }
- public byte ReadByte(Stream stream)
- {
- if (buffer.Length == 0)
- {
- return 0;
- }
- var len = 0;
- while (len < 1)
- {
- if (bufPos >= bufLen)
- {
- bufLen = stream.Read(buffer, 0, buffer.Length);
- bufPos = 0;
- if (bufLen == 0)
- {
- break; // EOF
- }
- }
- var bytesToRead = Math.Min(1, bufLen - bufPos);
- if (bytesToRead == 0)
- {
- break;
- }
- if (bytesToRead > 0)
- {
- len += bytesToRead;
- }
- }
- return buffer[bufPos++];
- }
- public string? Read(Stream stream, int charCount)
- {
- if (charCount < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(charCount));
- }
- if (charCount == 0)
- {
- return string.Empty;
- }
- var len = 0;
- var dataRead = false;
- var resultBuffer = ArrayPool<char>.Shared.Rent(charCount);
- try
- {
- while (len < charCount)
- {
- if (bufPos >= bufLen)
- {
- bufLen = stream.Read(buffer, 0, buffer.Length);
- bufPos = 0;
- if (bufLen == 0)
- {
- break; // EOF
- }
- }
- ReadOnlySpan<byte> byteSpan = new(buffer, bufPos, bufLen - bufPos);
- Span<char> charSpan = new(resultBuffer, len, charCount - len);
- decoder ??= Encoding.UTF8.GetDecoder();
- decoder.Convert(
- byteSpan,
- charSpan,
- false,
- out var bytesUsed,
- out var charsUsed,
- out _);
- if (charsUsed > 0)
- {
- len += charsUsed;
- dataRead = true;
- }
- bufPos += bytesUsed;
- if (bytesUsed == 0)
- {
- break;
- }
- }
- if (!dataRead || len != charCount)
- {
- return null;
- }
- return resultBuffer.AsSpan(0, len).ToString();
- }
- finally
- {
- ArrayPool<char>.Shared.Return(resultBuffer);
- }
- }
- static void AppendToBuffer(ref byte[] buffer, ReadOnlySpan<byte> segment, ref int length)
- {
- if (length + segment.Length > buffer.Length)
- {
- var newSize = Math.Max(buffer.Length * 2, length + segment.Length);
- var newBuffer = ArrayPool<byte>.Shared.Rent(newSize);
- Array.Copy(buffer, newBuffer, length);
- ArrayPool<byte>.Shared.Return(buffer);
- buffer = newBuffer;
- }
- segment.CopyTo(buffer.AsSpan(length));
- length += segment.Length;
- }
- public void Clear()
- {
- bufPos = 0;
- bufLen = 0;
- }
- public string? ReadNumber(Stream stream)
- {
- var resultBuffer = ArrayPool<char>.Shared.Rent(64); // Numbers shouldn't be too long
- var len = 0;
- var hasStarted = false;
- var isHex = false;
- var hasDecimal = false;
- var lastWasE = false;
- try
- {
- // Skip leading whitespace
- while (true)
- {
- var b = PeekByte(stream);
- if (b == -1)
- {
- return null; // EOF
- }
- var c = (char)b;
- if (!char.IsWhiteSpace(c))
- {
- break;
- }
- ReadByte(stream); // Consume whitespace
- }
- // Check for hex prefix at the start
- if (PeekByte(stream) == '0')
- {
- var nextByte = PeekByte(stream, 1);
- if (nextByte == 'x' || nextByte == 'X')
- {
- isHex = true;
- resultBuffer[len++] = '0';
- ReadByte(stream);
- resultBuffer[len++] = (char)ReadByte(stream);
- hasStarted = true;
- }
- }
- // Read number characters
- while (true)
- {
- var b = PeekByte(stream);
- if (b == -1)
- {
- break; // EOF
- }
- var c = (char)b;
- var shouldConsume = false;
- if (!hasStarted && (c == '+' || c == '-'))
- {
- shouldConsume = true;
- hasStarted = true;
- }
- else if (isHex)
- {
- // Hex digits
- if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))
- {
- shouldConsume = true;
- hasStarted = true;
- }
- // Hex decimal point
- else if (c == '.' && !hasDecimal)
- {
- shouldConsume = true;
- hasDecimal = true;
- }
- // Hex exponent (p or P)
- else if ((c == 'p' || c == 'P') && hasStarted)
- {
- shouldConsume = true;
- lastWasE = true;
- }
- // Sign after exponent
- else if (lastWasE && (c == '+' || c == '-'))
- {
- shouldConsume = true;
- lastWasE = false;
- }
- }
- else
- {
- // Decimal digits
- if (c >= '0' && c <= '9')
- {
- shouldConsume = true;
- hasStarted = true;
- lastWasE = false;
- }
- // Decimal point
- else if (c == '.' && !hasDecimal)
- {
- shouldConsume = true;
- hasDecimal = true;
- lastWasE = false;
- }
- // Exponent (e or E)
- else if ((c == 'e' || c == 'E') && hasStarted)
- {
- shouldConsume = true;
- lastWasE = true;
- }
- // Sign after exponent
- else if (lastWasE && (c == '+' || c == '-'))
- {
- shouldConsume = true;
- lastWasE = false;
- }
- }
- if (shouldConsume)
- {
- if (len >= resultBuffer.Length)
- {
- // Number too long, expand buffer
- var newBuffer = ArrayPool<char>.Shared.Rent(resultBuffer.Length * 2);
- resultBuffer.AsSpan(0, len).CopyTo(newBuffer);
- ArrayPool<char>.Shared.Return(resultBuffer);
- resultBuffer = newBuffer;
- }
- resultBuffer[len++] = c;
- ReadByte(stream); // Consume the byte
- }
- else
- {
- break; // Not part of the number
- }
- }
- return len == 0 ? null : resultBuffer.AsSpan(0, len).ToString();
- }
- finally
- {
- ArrayPool<char>.Shared.Return(resultBuffer);
- }
- }
- int PeekByte(Stream stream, int offset = 0)
- {
- // Ensure we have enough data in buffer
- while (bufPos + offset >= bufLen)
- {
- if (bufLen == 0 || bufPos == bufLen)
- {
- bufLen = stream.Read(buffer, 0, buffer.Length);
- bufPos = 0;
- if (bufLen == 0)
- {
- return -1; // EOF
- }
- }
- else
- {
- // We need more data but buffer has some - this shouldn't happen with small offsets
- return -1;
- }
- }
- return buffer[bufPos + offset];
- }
- public void Dispose()
- {
- scratchBufferUsed = false;
- }
- }
|