view Implab/Parsing/Scanner.cs @ 55:c0bf853aa04f

Added initial JSON support +JSONParser +JSONWriter
author cin
date Sun, 15 Jun 2014 19:39:11 +0400
parents
children 7759c80cad95
line wrap: on
line source

using Implab;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Implab.Parsing {
    /// <summary>
    /// Базовый класс для разбора потока входных символов на токены.
    /// </summary>
    /// <remarks>
    /// Сканнер имеет внутри буффер с симолами входного текста, по которому перемещаются два
    /// указателя, начала и конца токена, при перемещении искользуется ДКА для определения
    /// конца токена и допустимости текущего символа.
    /// </remarks>
    public class Scanner {
        struct ScannerConfig {
            public DFAStateDescriptior[] states;
            public int[] alphabetMap;
        }

        Stack<ScannerConfig> m_defs = new Stack<ScannerConfig>();

        DFAStateDescriptior[] m_states;
        int[] m_alphabetMap;

        protected DFAStateDescriptior 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;

        public Scanner(CDFADefinition definition, string text) {
            Safe.ArgumentNotNull(definition, "definition");
            Safe.ArgumentNotEmpty(text, "text");

            m_states = definition.States;
            m_alphabetMap = definition.Alphabet.GetTranslationMap();

            Feed(text.ToCharArray());
        }

        public Scanner(CDFADefinition definition) {
            Safe.ArgumentNotNull(definition, "definition");

            m_states = definition.States;
            m_alphabetMap = definition.Alphabet.GetTranslationMap();

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

            m_pointer = -1;
            m_buffer = data;
            m_bufferSize = length;
            Shift();
        }

        /// <summary>
        /// Получает текущий токен в виде строки.
        /// </summary>
        /// <returns></returns>
        public string GetTokenValue() {
            return new String(m_buffer, m_tokenOffset, m_tokenLen);
        }

        /// <summary>
        /// Метки текущего токена, которые были назначены в регулярном выражении.
        /// </summary>
        public int[] TokenTags {
            get {
                return m_currentState.tag;
            }
        }

        /// <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[CDFADefinition.INITIAL_STATE];
            m_tokenLen = 0;
            m_tokenOffset = m_pointer;
            int nextState = CDFADefinition.UNREACHEBLE_STATE;
            do {
                nextState = m_currentState.transitions[m_previewCode];
                if (nextState == CDFADefinition.UNREACHEBLE_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) {
                return ReadNextChunk();
            }

            m_previewCode = m_alphabetMap[m_buffer[m_pointer]];

            return true;
        }

        /// <summary>
        /// Вызывается по достижению конца входного буффера для получения
        /// новых данных.
        /// </summary>
        /// <returns><c>true</c> - новые двнные получены, можно продолжать обработку.</returns>
        protected virtual bool ReadNextChunk() {
            return false;
        }

        /// <summary>
        /// Позиция сканнера во входном буфере
        /// </summary>
        public int Position {
            get {
                return m_pointer + 1;
            }
        }

        /// <summary>
        /// Преключает внутренний ДКА на указанный, позволяет реализовать подобие захватывающей
        /// группировки.
        /// </summary>
        /// <param name="states">Таблица состояний нового ДКА</param>
        /// <param name="alphabet">Таблица входных символов для нового ДКА</param>
        protected void Switch(DFAStateDescriptior[] 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]];
        }
    }
}