Mercurial > pub > ImplabNet
diff Implab/Automaton/Scanner.cs @ 162:0526412bbb26 ref20160224
DFA refactoring
author | cin |
---|---|
date | Wed, 24 Feb 2016 08:39:53 +0300 |
parents | |
children | ec35731ae299 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Implab/Automaton/Scanner.cs Wed Feb 24 08:39:53 2016 +0300 @@ -0,0 +1,259 @@ +using Implab; +using System; +using System.Collections.Generic; +using System.IO; +using Implab.Components; + +namespace Implab.Automaton { + /// <summary> + /// Базовый класс для разбора потока входных символов на токены. + /// </summary> + /// <remarks> + /// Сканнер имеет внутри буффер с симолами входного текста, по которому перемещаются два + /// указателя, начала и конца токена, при перемещении искользуется ДКА для определения + /// конца токена и допустимости текущего символа. + /// </remarks> + public abstract class Scanner<TTag> : Disposable { + struct ScannerConfig { + public DFAStateDescriptior<TTag>[] states; + public int[] alphabetMap; + } + + Stack<ScannerConfig> m_defs = new Stack<ScannerConfig>(); + + DFAStateDescriptior<TTag>[] m_states; + int[] m_alphabetMap; + + protected DFAStateDescriptior<TTag> m_currentState; + int m_previewCode; + + protected int m_tokenLen = 0; + 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(DFAStateDescriptior<TTag>[] states, int[] alphabet) { + Safe.ArgumentNotEmpty(states, "states"); + Safe.ArgumentNotNull(alphabet, "alphabet"); + + m_states = states; + m_alphabetMap = alphabet; + + Feed(new char[0]); + } + + /// <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.tag; + } + } + + /// <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_states[DFADefinition.INITIAL_STATE]; + 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; + else + throw new ParserException( + String.Format( + "Unexpected symbol '{0}', at pos {1}", + m_buffer[m_pointer], + Position + ) + ); + } else { + m_currentState = m_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_alphabetMap[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="states">Таблица состояний нового ДКА</param> + /// <param name="alphabet">Таблица входных символов для нового ДКА</param> + protected void Switch(DFAStateDescriptior<TTag>[] states, int[] alphabet) { + Safe.ArgumentNotNull(states, "dfa"); + + m_defs.Push(new ScannerConfig { + states = m_states, + alphabetMap = m_alphabetMap + }); + + m_states = states; + m_alphabetMap = alphabet; + + m_previewCode = m_alphabetMap[m_buffer[m_pointer]]; + } + + /// <summary> + /// Восстанавливает предыдущей ДКА сканнера. + /// </summary> + protected void Restore() { + if (m_defs.Count == 0) + throw new InvalidOperationException(); + var prev = m_defs.Pop(); + m_states = prev.states; + m_alphabetMap = prev.alphabetMap; + m_previewCode = m_alphabetMap[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); + } + } +}