Forum.iFiction.Ru

iFiction.Ru · ifHub · FAQ · IFWiki · QSP · URQ · INSTEAD · AXMA

форум об interactive fiction, текстовых приключенческих играх и всём таком...

Вы не зашли.

0    0    #1
19.03.2008 15:29

Eten
Участник (+9, -307)
Откуда: Балаково, Санкт-Петербург.
Зарегистрирован: 21.05.2007
Сообщений: 1416
Вебсайт

---

Базовый класс не прямого анализатора

Для начало скажу, что не прямой анализатор - это анализатор с возможностью отката при не удачной попытке распознать лексему. Он очень удобен для развивающихся языков и прост в создании и улучшении. Единственный недостаток - он работает медленнее прямого анализатора. Но этот недостаток всегда легко устраним.

Теперь о самом базовом классе анализатора. Все что нужно базовому классу, так это: считывание следующего символа во входном коде, откат, откат на один символ и обнуление отката. Из числа переменных, следующие: считанный символ, значение лексемы (к примеру название идентификатора), размер буффера для хранения значения лексемы, текущая позиция (строка и столбец) при чтении символа из вх. кода, кол-во пройденных строк и стоблцов при распозновании лексемы для возможности ее отката, код ошибки произошедший в случае не распознования лексемы (в данном случае допущенной ошибки со стороны пользователя), сам буффер входного кода ввиде массива и переменная достижения конца вх. кода.

Всего этого достаточно для того, чтобы на основе даного базового класса построить свой конечный класс, в которым вы определяете методы проверки принадлежности символа к определенным классам символов (к примеру класс чисел, игнорируемых и пробельных символов и т.д.), там же прописываете все методы для запуска анализатора и выдачи списка готовых и разобранных лексем, которые нужны для синтаксического анализа.

Нужно учитывать некоторые ньюансы.

Во-первых, перед запуском лексического анализатора (именно для него и создан данный класс), присвоить текущей строке ноль, а столбцу -1 и не трогать в процессе лексического разбора переменную тек. поз., т.о. устанавливается начальная позиция пера для считывания первого символа.

Во-вторых, перед запуском анализатора, нужно ввести значение для массива вх.кода, иначе вы можете вызвать неуправляемое исключение. Такое исключение не имеет смысла отлавливать в базовом классе, т.к. перед считыванием символа из вх.кода, массив вх. кода не должен быть пустым, иначе работа анализатора лишена всякого смысла.

В-третьих, чтобы не сделать бесконечный цикл, нжуно использовать переменную достижения конца файла, т.к. при истине данной переменной считываемый последний символ останется тем же и вы будете постоянно достигать конца вх.кода, хотя ошибки это не вызовет.

И в-четвертых, данный анализатор нужно использовать для распознования лексем в последовательном порядке, а не хаотичном. Т.е. распознование символов пробела и игнорируемых, а также лексем комментария стоит ставить на проверку раньше остальных.

Данного объяснения на мой взгляд достаточно для тех, кто уже имел дело с анализатора вх.кода или хотя бы читал о них.

Неактивен

0    0    #2
19.03.2008 15:49

Gremour
Участник (+1)
Откуда: Беларусь
Зарегистрирован: 09.11.2004
Сообщений: 234

Re: Базовый класс не прямого анализатора

Начну от частного к общему.

Eten написал:

размер буффера для хранения значения лексемы,

Я думал, что современные языки программирования (венцом которых, несомненно, является Си шарп ;) свободны от такой чепухи, как ручное выделение памяти под строки. Или я ошибался?

текущая позиция (строка и столбец) при чтении символа из вх. кода,

Текст (в частности и особенно, XML) для алгоритма разбора удобнее воспринимать линейным потоком символов, нежели двумерным массивом со стоками и колонками. Кстати, раньше ты говорил, что твоему анализатору всё равно откуда подают текст. Так вот, в цивилизованном мире давно уже пользуются потоками (stream) для этих целей, а не передают массивом в памяти.

Даже если ты пишешь абстрактный анализатор, он должен работать со словарём, а не с отдельными символами.

Сдаётся мне, что СТК начал буксовать и ты пытаешься спасти наработанное для будущих поколений. Скажи, что я не прав. %)

Неактивен

0    0    #3
19.03.2008 15:56

Gesperid
Участник
Зарегистрирован: 25.02.2005
Сообщений: 106

Re: Базовый класс не прямого анализатора

Eten написал:

...Много букофф...

А не проще выложить интерфейс класса?

Отредактировано Gesperid (19.03.2008 15:56)

Неактивен

0    0    #4
19.03.2008 16:00

Eten
Участник (+9, -307)
Откуда: Балаково, Санкт-Петербург.
Зарегистрирован: 21.05.2007
Сообщений: 1416
Вебсайт

---

Re: Базовый класс не прямого анализатора

А теперь сам код базового класса на СИ шарпе.

using System;
using System.Collections.Generic;
using System.Text;

namespace EngineSTK1
{
    public class Analisator
    {
        #region Поля класса AnalisatorLexema
      /// <summary>
        /// Размер буфера для значения лексемы.
        /// </summary>
 
        protected const int bufsize = 256;
       /// <summary>
        /// Тип ошибки
        /// </summary>

        protected ErrorCode er;
         /// <summary>
        /// Текущий читаемый символ.
        /// </summary>

        protected char symbol;
        /// <summary>
        /// Значение лексемы.
        /// </summary>

        protected StringBuilder lexvalue = new StringBuilder(bufsize);
        /// <summary>
        /// Начальная позиция при считывании лексемы.
        /// </summary>

        protected Position start_lx;
       /// <summary>
        /// Обозначает конец входной ленты
        /// </summary>

        protected bool Eof;
       /// <summary>
        /// Текущая позиция в входном коде СТК.
        /// </summary>

        protected Position current_pos;
       /// <summary>
        /// Кол-во пройденных строк и столбцов при чтении лексемы
        /// </summary>

        protected Position unset_pos;
        /// <summary>
        /// Входной код
        /// </summary>

        protected string[] ec;
        #endregion
        #region Свойства класса AnalisatorLexema
       /// <summary>
        /// Выдает тип, ошибки. Если выдано None, значит разбор прошел успешно.
        /// </summary>

        public ErrorCode ErrorType
        {
            get { return this.er; }
        }
        /// <summary>
        /// Входной код
        /// </summary>

        public string[] EntranceCode
        {
            get { return this.ec; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("EntranceCode", "Ошибка! Была попытка ввода пустого значения");
               //Загрузка входного кода
                this.ec = new string[value.Length];
                int i = 0;
                foreach (string s in value)
                {
                    this.ec[i] = s;
                    i++;
                }
            }
        }
        #endregion
        #region Методы
        /// <summary>
        /// Конструктор
        /// </summary>

        public Analisator()
        {
           //инициализация внутренних переменных
            this.er = ErrorCode.None;
            this.ec = null;
            this.symbol = ' ';
        }

        /// <summary>
        /// Обнуление отката
        /// </summary>

        protected void Unset_Clear()
        {
            this.unset_pos.Line = 0;
            this.unset_pos.Column = 0;
        }
        /// <summary>
        /// Считывает следующий символ во входном коде.
        /// </summary>

        protected void NetxtSymbol()
        {
            //проверка на конец входного кода
            if (this.current_pos.Line >= this.ec.Length)
            {
                this.Eof = true;
                this.symbol = ' ';
                return;
            }
            // Считывает символ в указанной позиции
            this.symbol = this.ec[this.current_pos.Line][++this.current_pos.Column];
            //кол-во считанных столбцов
            this.unset_pos.Column++;
            //если позиция столбца достигла конца строки, то переходим на следующую строку
            //и устанавливаем перо столбца на ноль

            if (this.current_pos.Column >= this.ec[this.current_pos.Line].Length - 1)
            {
                this.current_pos.Line++;
                this.unset_pos.Line++;
                this.current_pos.Column = -1;
            }
        }
        /// <summary>
        /// Производит откат на один символ
        /// </summary>

        protected void Unset_Odin()
        {
            if (!this.Eof)
            {
                //Чтобы последний не принятый за число символ не пропал при следующем формировании лексемы, нужно сдвинуть перо назад на 1
                if (this.current_pos.Column <= 0)
                {//если после прочтения перо перешло на новую строку, то вернуть обратно и отнять единицу от длины строки для след. прочтения искомого символа
                    this.current_pos.Line -= 1;
                    this.current_pos.Column = this.ec[this.current_pos.Line].Length - 2;
                }
                else //в противном случае сдвинуть перо только на 1 столбец влево
                    this.current_pos.Column -= 1;
                //для позиции отката
                if (this.unset_pos.Column <= 0)
                {
                    this.unset_pos.Line -= 1;
                    this.unset_pos.Column -= 1;
                }
                else
                    this.unset_pos.Column -= 1;
            }
        }
        /// <summary>
        /// Производит откат назад при неудачной попытке распознать символ.
        /// </summary>

        protected void Unset()
        {
            //возвращаем перо в положение перед чтением лексемы
            //если возврат произошел, когда перо перешло на новую строчку, то выполняем соответствующие действия

            if (this.current_pos.Column == -1)
            {
                this.current_pos.Line--;
                //в данном случае вычитываем не из длинны, а из позиции длину прочтенных символов
                this.current_pos.Column = (this.ec[this.current_pos.Line].Length - 1) - this.unset_pos.Column;
                //если условие истино, то значит сдвиг на несколько строк
                if (this.current_pos.Column < 0)
                {
                    this.current_pos.Column = Math.Abs(this.current_pos.Column);
                    while (this.unset_pos.Line > 0)
                    {
                        this.current_pos.Line--;
                        this.current_pos.Column = Math.Abs((this.ec[this.current_pos.Line].Length - 1) - this.current_pos.Column);
                        this.unset_pos.Line--;
                        if (this.current_pos.Column >= -1)
                            break;
                    }
                }
                //обнуляем откат, т.к. он уже больше не нужен
                this.Unset_Clear();
                return;
            }
            //Если откат столбца меньше длины позиции пера, значит не было перехода на другую строку и достаточно сделать откат по столбцам
            if (this.unset_pos.Column <= this.current_pos.Column + 1)
                this.current_pos.Column -= this.unset_pos.Column;
            else//если нет, значит нужно сделать откат столбцов учитывая столбцы во всех пройденных строках
            {
                //сначало отнимаем unset, в данном случае учитывается что current_pos.Column, что явлвяется позицией от 0 до длина - 1
                //получаем модуль разности для того, чтобы в последующих вычитаниях не получит сложение.

                this.current_pos.Column = Math.Abs(this.current_pos.Column - this.unset_pos.Column);
                while (this.unset_pos.Line > 0)
                {
                    this.current_pos.Line--;
                    this.current_pos.Column = Math.Abs(this.ec[this.current_pos.Line].Length - this.current_pos.Column);
                    this.unset_pos.Line--;                   
                }
                //если равен, то сдвигаем на следующую строку с позицией столбца -1
                if (this.current_pos.Column + 1 == this.ec[this.current_pos.Line].Length - 1 && this.current_pos.Line < this.ec.Length)
                {
                    this.current_pos.Line++;
                    this.current_pos.Column = -1;
                }
            }
            //обнуляем откат, т.к. он уже больше не нужен
            this.Unset_Clear();
        }
        #endregion
    }
}

Для полного понимания привожу код Position и перечисления ErrorCode:

    /// <summary>
    /// Задает позицию во входном коде СТК.
    /// </summary>

    public struct Position
    {
        /// <summary>
        /// Номер строки в входном коде.
        /// </summary>

        public int Line;
        /// <summary>
        /// Столбец строки во входном коде.
        /// </summary>

        public int Column;
    }

    /// <summary>
    /// Все типы встречаемых ошибок в СТК.
    /// </summary>

    public enum ErrorCode : int
    {
        /// <summary>
        /// Нет никакой ошибки
        /// </summary>

        None = 0,
        #region Во время выполнения AnalisatorLexema
        /// <summary>
        /// Не был закончен парный комментарий
        /// </summary>

        lexComment = 1,
        /// <summary>
        /// Нарушен формат записи десятичного числа
        /// </summary>

        lexNumber = 2,
        /// <summary>
        /// Нарушен формат записи строки символов
        /// </summary>

        lexStringConst = 3,
        /// <summary>
        /// Лексический анализатор не смог определить лексему, т.к такой нет в СТК.
        /// </summary>

        lexInvalid = 4,
        #endregion
    ...
   }

Коды ошибок можете вставлять полностью свои, кроме первого нулевого кода None.

Также, не забывайте об особенности в СИ шарп, в котором объекты не имеют функции уничтожения наряду с конструктором. Объект этого класса удалется из памяти, как только на него перестают ссылаться другие, проще говоря достатоно присвоить данному объекту null для очистки его из памяти.

В заключение скажу, что данный алгоритм анализатора имеет один существенный недостаток. А он заключается в том, что вы ограничены манипуляцией пера только данными в базовом классе методами, но это ограничение разумно и не доставляет сильных неудобств. Т.к. всегда можно придумать алгоритм вашего конечного анализатора на основе этого так, чтобы данные минусы теряли свою актуальность и не портили весь алгоритм.

Также, стоит заметить, что мною данный анализатор был использован для конечных анализаторов команд и тэгов, справился без ошибок и больших нагрузок на память, скорость приемлемого качества и сильно облегчил отладку и разработку конечных анализаторов. Так, что буду рад, если данный класс поможет и вам.

Неактивен

0    0    #5
19.03.2008 16:31

Eten
Участник (+9, -307)
Откуда: Балаково, Санкт-Петербург.
Зарегистрирован: 21.05.2007
Сообщений: 1416
Вебсайт

---

Re: Базовый класс не прямого анализатора

как ручное выделение памяти под строки

Это сильно не влияет на программу и сделанно из соображений экономии, т.к. намного проще выделить сразу нужный объем, а когда он станет маленьким, то расширится до нужного размера.

Кстати, раньше ты говорил, что твоему анализатору всё равно откуда подают текст

Обрати внимание на поле ec. Оно позволяет считывать текст от любого источника. В любом другом случае, код базового класса проще паренной репы  и его можно при желании переделать для Stream, но стоит напомнить, что есть еще Text в котором учитываются строки и столбец.
А также данный анализатор, должен позволять указывать точную начальную позицию для каждой лексемы. Так что, все выше указанное здесь,  имеет свой смысл быть и использоваться.

Даже если ты пишешь абстрактный анализатор, он должен работать со словарём, а не с отдельными символами.

Если правильно понял, то ты имел ввиду со словарем лексем, то такой словарь должен составляться в конечном анализаторе, а не в базовом. С другой стороны лексический анализ потому и является лексический, что считывает по символьно, а не хранит целые словари не пойми для чего. Считывание по символьно это сдантарт для любого нормального анализатора, к тому же при сипользовании именно такого анлизатора, не испытваю затруднений при распозновании чисел, идентификаторов и прочей лабуды. Так, что смысл применения словаря в данном предложении совершенно не понятен.

Сдаётся мне, что СТК начал буксовать и ты пытаешься спасти наработанное для будущих поколений. Скажи, что я не прав. %)

lol Рано радуешься и глубоко ошибаешься. Если бы такое действительно случилось, тогда это означало бы не работоспособность моего кода, что как показала практика не является правдой, а только вымыслом некоторых лиц. К тому же медлительность моей разработки обусловлена тем, что всегда пишу код не абы как, а с возможностью быстрой переделки и дальнейшим апгрейдом с возможностью его расширения. Естественное такой хороший код за два дня не напишешь, да еще приходиться все время забегать вперед. Иначе говоря, получается своего рода маленький шедевр, который можно либо целиком использовать, как библиотеку, либо некоторые части для других проектов (Данная тема потверждает данное предложение). Один движок маленькой игры у меня есть, там же есть некоторые классы и весь опыт работы с алфавитами и словарями, что дает мне возможность использовать тот же алфавит для интернационализации STK1 в командах, тэгах, и использумеом буквенном алфавите. При этом стоит учесть, что класс Алфавита разрабтывался вовсе не для анализатора, что в принципе не мешает ему быть использованным здесь - это я и называю хорошим программированием и разработкой классов в одном флаконе, также делают и остальные.

Просьба показать сие движок не имеет смысла, т.к. у меня нет художника могущего нарисовать настоящий и красивый дизайн для данной и очень простой игры, поэтому движок с мануалом лежит до лучших времен, а все серенькие и консольные варианты давно выкинул в мусорку... Но мы здесь говорим так сказать не об этом движке, а об базовом классе, так что вернемся к теме. И давайте будем обсуждать именно базовый класс, а не СТК. Для этого можно завести другую тему, а не мусорить в этой.

Неактивен

0    0    #6
19.03.2008 18:18

Gremour
Участник (+1)
Откуда: Беларусь
Зарегистрирован: 09.11.2004
Сообщений: 234

Re: Базовый класс не прямого анализатора

Eten написал:

Так, что смысл применения словаря в данном предложении совершенно не понятен.

У тебя нет механизмов поддержки словаря. Отдельные символы текста интереса для анализа не представляют. Интерес представляют слова и словосочетания. В нашем случае нужен именно анализатор слов.

класс Алфавита разрабтывался вовсе не для анализатора, что в принципе не мешает ему быть использованным здесь - это я и называю хорошим программированием и разработкой классов в одном флаконе, также делают и остальные.

Я называю программирование хорошим, если в результате программа отлично выполняет то, что от неё требуется. Если при этом ещё и в исходник не страшно заглянуть, то вообще замечательно.

Неактивен

0    0    #7
19.03.2008 20:51

Eten
Участник (+9, -307)
Откуда: Балаково, Санкт-Петербург.
Зарегистрирован: 21.05.2007
Сообщений: 1416
Вебсайт

---

Re: Базовый класс не прямого анализатора

В нашем случае нужен именно анализатор слов.

То, что мной предложенно исходит из понятия технической темы, в разделе флейм в теме "Может выложить исходники хорошего анализатора?" я довольно четко описал, что хотел выложить. Так, что в нашем случае анализ слов не приводится, приводится лишь базовый класс анализатора входного кода, иначе называемый лексическим анализатором. Так, что дружище со словарем влез не в тему.

А с другой стороны можно на основе этого базового написать твой конечный анализатор слов, но у меня сейчас вниманию уделено другому.

Отдельные символы текста интереса для анализа не представляют. Интерес представляют слова и словосочетания.

Опять же повторюсь, говорим о разных вещях. Если тебе так нужен базовый анализатор слов, то напиши, какие проблемы. Хоть на основе этого, хоть свой с нуля.

А вот с символами ты брат ошибаешься, можно легко сделать анализ на низком уровне, а потом на высоком. Низкий уровень будет проще реализовать производным от базового класса, высокий уровень в другом классе или все в одном. Вариантов куча, бери какой удобнее.

З.Ы.
Приведенный класс всего лишь базовый и на его основе можно создать любой анализатор с нужными вам функциями.

Неактивен

Powered by PunBB
© copyright 2001–2024 iFiction.Ru