##// END OF EJS Templates
Working on text scanner
Working on text scanner

File last commit:

r172:92d5278d1b10 ref20160224
r173:ecfece82ca11 ref20160224
Show More
Scanner.cs
255 lines | 9.2 KiB | text/x-csharp | CSharpLexer
using Implab;
using System;
using System.Collections.Generic;
using System.IO;
using Implab.Components;
using Implab.Automaton.RegularExpressions;
namespace Implab.Automaton {
/// <summary>
/// Базовый класс для разбора потока входных символов на токены.
/// </summary>
/// <remarks>
/// Сканнер имеет внутри буффер с симолами входного текста, по которому перемещаются два
/// указателя, начала и конца токена, при перемещении искользуется ДКА для определения
/// конца токена и допустимости текущего символа.
/// </remarks>
public abstract class Scanner<TTag> : Disposable {
protected struct ScannerConfig {
public readonly DFAStateDescriptor<TTag>[] states;
public readonly int[] alphabet;
public readonly int initialState;
public ScannerConfig(DFAStateDescriptor<TTag>[] states, int[] alphabet, int initialState) {
this.initialState = initialState;
this.alphabet = alphabet;
this.states = states;
}
}
Stack<ScannerConfig> m_defs = new Stack<ScannerConfig>();
ScannerConfig m_config;
protected DFAStateDescriptor<TTag> m_currentState;
int m_previewCode;
protected int m_tokenLen;
protected int m_tokenOffset;
protected char[] m_buffer;
protected int m_bufferSize;
protected int m_pointer;
TextReader m_reader;
bool m_disposeReader;
int m_chunkSize = 1024; // 1k
int m_limit = 10 * 1024 * 1024; // 10Mb
protected Scanner(ScannerConfig config) {
Safe.ArgumentNotEmpty(config.states, "config.states");
Safe.ArgumentNotNull(config.alphabet, "config.alphabet");
m_config = config;
}
/// <summary>
/// Заполняет входными данными буффер.
/// </summary>
/// <param name="data">Данные для обработки.</param>
/// <remarks>Копирование данных не происходит, переданный массив используется в
/// качестве входного буффера.</remarks>
public void Feed(char[] data) {
Safe.ArgumentNotNull(data, "data");
Feed(data, data.Length);
}
/// <summary>
/// Заполняет буффур чтения входными данными.
/// </summary>
/// <param name="data">Данные для обработки.</param>
/// <param name="length">Длина данных для обработки.</param>
/// <remarks>Копирование данных не происходит, переданный массив используется в
/// качестве входного буффера.</remarks>
public void Feed(char[] data, int length) {
Safe.ArgumentNotNull(data, "data");
Safe.ArgumentInRange(length, 0, data.Length, "length");
AssertNotDisposed();
m_pointer = -1;
m_buffer = data;
m_bufferSize = length;
Shift();
}
public void Feed(TextReader reader, bool dispose) {
Safe.ArgumentNotNull(reader, "reader");
AssertNotDisposed();
if (m_reader != null && m_disposeReader)
m_reader.Dispose();
m_reader = reader;
m_disposeReader = dispose;
m_pointer = -1;
m_buffer = new char[m_chunkSize];
m_bufferSize = 0;
Shift();
}
/// <summary>
/// Получает текущий токен в виде строки.
/// </summary>
/// <returns></returns>
protected string GetTokenValue() {
return new String(m_buffer, m_tokenOffset, m_tokenLen);
}
/// <summary>
/// Метки текущего токена, которые были назначены в регулярном выражении.
/// </summary>
protected TTag[] TokenTags {
get {
return m_currentState.tags;
}
}
/// <summary>
/// Признак конца данных
/// </summary>
public bool EOF {
get {
return m_pointer >= m_bufferSize;
}
}
/// <summary>
/// Читает следующий токен, при этом <see cref="m_tokenOffset"/> указывает на начало токена,
/// <see cref="m_tokenLen"/> на длину токена, <see cref="m_buffer"/> - массив символов, в
/// котором находится токен.
/// </summary>
/// <returns><c>false</c> - достигнут конец данных, токен не прочитан.</returns>
protected bool ReadTokenInternal() {
if (m_pointer >= m_bufferSize)
return false;
m_currentState = m_config.states[m_config.initialState];
m_tokenLen = 0;
m_tokenOffset = m_pointer;
int nextState;
do {
nextState = m_currentState.transitions[m_previewCode];
if (nextState == DFAConst.UNREACHABLE_STATE) {
if (m_currentState.final)
return true;
throw new ParserException(
String.Format(
"Unexpected symbol '{0}', at pos {1}",
m_buffer[m_pointer],
Position
)
);
}
m_currentState = m_config.states[nextState];
m_tokenLen++;
} while (Shift());
// END OF DATA
if (!m_currentState.final)
throw new ParserException("Unexpected end of data");
return true;
}
bool Shift() {
m_pointer++;
if (m_pointer >= m_bufferSize) {
if (!ReadNextChunk())
return false;
}
m_previewCode = m_config.alphabet[m_buffer[m_pointer]];
return true;
}
bool ReadNextChunk() {
if (m_reader == null)
return false;
// extend buffer if nesessary
if (m_pointer + m_chunkSize > m_buffer.Length) {
// trim unused buffer head
var size = m_tokenLen + m_chunkSize;
if (size >= m_limit)
throw new ParserException(String.Format("Input buffer {0} bytes limit exceeded", m_limit));
var temp = new char[size];
Array.Copy(m_buffer, m_tokenOffset, temp, 0, m_tokenLen);
m_pointer -= m_tokenOffset;
m_bufferSize -= m_tokenOffset;
m_tokenOffset = 0;
m_buffer = temp;
}
var read = m_reader.Read(m_buffer, m_tokenLen, m_chunkSize);
if (read == 0)
return false;
m_bufferSize += read;
return true;
}
/// <summary>
/// Позиция сканнера во входном буфере
/// </summary>
public int Position {
get {
return m_pointer + 1;
}
}
/// <summary>
/// Преключает внутренний ДКА на указанный, позволяет реализовать подобие захватывающей
/// группировки.
/// </summary>
/// <param name = "config"></param>
protected void Switch(ScannerConfig config) {
Safe.ArgumentNotNull(config.states, "config.states");
m_defs.Push(m_config);
m_config = config;
m_previewCode = m_config.alphabet[m_buffer[m_pointer]];
}
/// <summary>
/// Восстанавливает предыдущей ДКА сканнера.
/// </summary>
protected void Restore() {
if (m_defs.Count == 0)
throw new InvalidOperationException();
m_config = m_defs.Pop();
m_previewCode = m_config.alphabet[m_buffer[m_pointer]];
}
protected override void Dispose(bool disposing) {
if (disposing) {
if (m_reader != null && m_disposeReader)
m_reader.Dispose();
m_buffer = null;
m_bufferSize = 0;
m_pointer = 0;
m_tokenLen = 0;
m_tokenOffset = 0;
}
base.Dispose(disposing);
}
}
}