view Implab/Automaton/Scanner.cs @ 172:92d5278d1b10 ref20160224

Working on text scanner
author cin
date Mon, 14 Mar 2016 01:19:38 +0300
parents e227e78d72e4
children 983df35b3ca1
line wrap: on
line source

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);
        }
    }
}