Flee
This commit is contained in:
226
Parsing/LookAheadReader.cs
Normal file
226
Parsing/LookAheadReader.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
namespace Flee.Parsing
|
||||
{
|
||||
// * A look-ahead character stream reader. This class provides the
|
||||
// * functionalities of a buffered line-number reader, but with the
|
||||
// * additional possibility of peeking an unlimited number of
|
||||
// * characters ahead. When looking further and further ahead in the
|
||||
// * character stream, the buffer is continously enlarged to contain
|
||||
// * all the required characters from the current position an
|
||||
// * onwards. This means that looking more characters ahead requires
|
||||
// * more memory, and thus becomes unviable in the end.
|
||||
internal class LookAheadReader : TextReader
|
||||
{
|
||||
private const int StreamBlockSize = 4096;
|
||||
private const int BufferBlockSize = 1024;
|
||||
private char[] _buffer = new char[StreamBlockSize];
|
||||
private int _pos;
|
||||
private int _length;
|
||||
private TextReader _input = null;
|
||||
private int _line = 1;
|
||||
private int _column = 1;
|
||||
|
||||
public LookAheadReader(TextReader input) : base()
|
||||
{
|
||||
this._input = input;
|
||||
}
|
||||
|
||||
public int LineNumber => _line;
|
||||
|
||||
public int ColumnNumber => _column;
|
||||
|
||||
public override int Read()
|
||||
{
|
||||
ReadAhead(1);
|
||||
if (_pos >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateLineColumnNumbers(1);
|
||||
return Convert.ToInt32(_buffer[System.Math.Max(System.Threading.Interlocked.Increment(ref _pos), _pos - 1)]);
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(char[] cbuf, int off, int len)
|
||||
{
|
||||
ReadAhead(len);
|
||||
if (_pos >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = _length - _pos;
|
||||
if (count > len)
|
||||
{
|
||||
count = len;
|
||||
}
|
||||
UpdateLineColumnNumbers(count);
|
||||
Array.Copy(_buffer, _pos, cbuf, off, count);
|
||||
_pos += count;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public string ReadString(int len)
|
||||
{
|
||||
ReadAhead(len);
|
||||
if (_pos >= _length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = _length - _pos;
|
||||
if (count > len)
|
||||
{
|
||||
count = len;
|
||||
}
|
||||
UpdateLineColumnNumbers(count);
|
||||
var result = new string(_buffer, _pos, count);
|
||||
_pos += count;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public override int Peek()
|
||||
{
|
||||
return Peek(0);
|
||||
}
|
||||
|
||||
public int Peek(int off)
|
||||
{
|
||||
ReadAhead(off + 1);
|
||||
if (_pos + off >= _length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Convert.ToInt32(_buffer[_pos + off]);
|
||||
}
|
||||
}
|
||||
|
||||
public string PeekString(int off, int len)
|
||||
{
|
||||
ReadAhead(off + len + 1);
|
||||
if (_pos + off >= _length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var count = _length - (_pos + off);
|
||||
if (count > len)
|
||||
{
|
||||
count = len;
|
||||
}
|
||||
return new string(_buffer, _pos + off, count);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
_buffer = null;
|
||||
_pos = 0;
|
||||
_length = 0;
|
||||
if (_input != null)
|
||||
{
|
||||
_input.Close();
|
||||
_input = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadAhead(int offset)
|
||||
{
|
||||
int size = 0;
|
||||
int readSize = 0;
|
||||
|
||||
// Check for end of stream or already read characters
|
||||
if (_input == null || _pos + offset < _length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove old characters from buffer
|
||||
if (_pos > BufferBlockSize)
|
||||
{
|
||||
Array.Copy(_buffer, _pos, _buffer, 0, _length - _pos);
|
||||
_length -= _pos;
|
||||
_pos = 0;
|
||||
}
|
||||
|
||||
// Calculate number of characters to read
|
||||
size = _pos + offset - _length + 1;
|
||||
if (size % StreamBlockSize != 0)
|
||||
{
|
||||
size = (size / StreamBlockSize) * StreamBlockSize;
|
||||
size += StreamBlockSize;
|
||||
}
|
||||
EnsureBufferCapacity(_length + size);
|
||||
|
||||
// Read characters
|
||||
try
|
||||
{
|
||||
readSize = _input.Read(_buffer, _length, size);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_input = null;
|
||||
throw;
|
||||
}
|
||||
|
||||
// Append characters to buffer
|
||||
if (readSize > 0)
|
||||
{
|
||||
_length += readSize;
|
||||
}
|
||||
if (readSize < size)
|
||||
{
|
||||
try
|
||||
{
|
||||
_input.Close();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_input = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureBufferCapacity(int size)
|
||||
{
|
||||
char[] newbuf = null;
|
||||
|
||||
if (_buffer.Length >= size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (size % BufferBlockSize != 0)
|
||||
{
|
||||
size = (size / BufferBlockSize) * BufferBlockSize;
|
||||
size += BufferBlockSize;
|
||||
}
|
||||
newbuf = new char[size];
|
||||
Array.Copy(_buffer, 0, newbuf, 0, _length);
|
||||
_buffer = newbuf;
|
||||
}
|
||||
|
||||
private void UpdateLineColumnNumbers(int offset)
|
||||
{
|
||||
for (int i = 0; i <= offset - 1; i++)
|
||||
{
|
||||
if (_buffer.Contains(_buffer[_pos + i]))
|
||||
{
|
||||
_line += 1;
|
||||
_column = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_column += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user