Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lab_tlpt_12_7.doc
Скачиваний:
1
Добавлен:
01.03.2025
Размер:
494.08 Кб
Скачать

Лабораторная работа № 3.

Лабораторная работа №1.3. Лексический анализатор.

Общие принципы лексического разбора, разбор ключевых слов, идентификаторов и числовых констант.

Для задания типа лексемы обычно используется перечисление, включающее в себя все возможные в исходном языке типы: ключевые слова, операторы, идентификаторы и числа.

public enum Lexems

{

None, Name, Number, Begin, End, If, Then, Multiplication, Division, Plus,

Equal, Less, LessOrEqual, Semi, Assign,LeftBracket,EOF, …

};

В рамках класса LexicalAnalyzer будем реализовывать методы лексического анализа: хранение ключевых слов языка и их распознавание, разбор текущей лексемы в зависимости от её типа.

Работа с ключевыми словами языка. Для ключевого слова языка объявим структуру Keyword, сопоставляющую ключевое слово с лексемой определенного типа.

private struct Keyword

{

public string word;

public Lexems lex;

}

Хранить ключевые слова будем в массиве keywords, для определения текущей позиции в массиве объявим поле keywordsPointer (в C# невозможно динамически изменить длину массива, поэтому нам нужно хранить текущую позицию,чтобы не обращаться к пустым элементам и не превысить заданную статически длину массива). Ниже представлен псевдокод методов добавления ключевого слова в массив и проверки, является ли идентфикатор ключевым словом.

добавитьКлючевоеСлово( ключевоеСлово, лексема )

{

Keyword kw = новый Keyword();

kw.word = ключевоеСлово;

kw.lex = лексема;

keywords[keywordsPointer++] = kw;

}

лексема получитьКлючевоеСлово( ключевоеСлово )

{

от keywordsPointer – 1 до 0

если( keywords[keywordsPointer].word == ключевоеСлово )

вернуть keywords[keywordsPointer].lex;

вернуть Lexems.Name;

}

Разбор лексем. Для хранения текущей лексемы и текущего идентификатора объявим соответствующие поля currentLexem и currentName. Основным методом анализатора будет public метод для разбора следующей лексемы. Для считывания следующего символа будем использовать метод читатьСледующийСимвол() класса Reader, реализованного на прошлой лабораторной работе.

разобратьСледующуюЛексему()

{

пока( Reader.текущийСимвол == пробел )

Reader. читатьСледующийСимвол();

если( Reader.текущийСимвол является буквой )

разобратьИдентификатор();

иначе если( Reader.текущийСимвол является цифрой )

разобратьЧисло();

иначе если( Reader.текущийСимвол == переводСтроки )

{

Reader.читатьСледующийСимвол();

currentLexem = Lexems.Разделитель;

}

иначе если( Reader.текущийСимвол == ‘<’ )

{

Reader.читатьСледующийСимвол();

если( Reader.текущийСимвол == ‘=’ )

{

Reader. читатьСледующийСимвол();

currentLexem = Lexems.МеньшеИлиРавно;

}

иначе

currentLexem = Lexems.Меньше;

}

иначе если( Reader.текущийСимвол == ‘+’ )

{

Reader. читатьСледующийСимвол();

currentLexem = Lexems.Плюс;

}

………………………………………………………………

Иначе

Ошибка.НедопустимыйСимвол

}

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

разобратьИдентификатор()

{

идентификатор = пустаяСтрока;

выполнять

{

идентификатор += Reader.текущийСимвол;

Reader. читатьСледующийСимвол();

}

пока( Reader.текущийСимвол является буквой );

currentName = идентификатор;

currentLexem = получитьКлючевоеСлово( идентификатор );

}

Для тестирования функционала класса LexicalAnalyzer можно использовать следующий код:

LexicalAnalyzer.инициализировать();

Пока(LexicalAnalyzer.текущаяЛексема != Lexems.конецФайла)

{

вывести LexicalAnalyzer.текущаяЛексема;

LexicalAnalyzer.разобратьСледующуюЛексему();

}

Замечания по коду:

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

  2. Метод разобратьЧисло() реализуется по аналогии с методом разобратьИдентификатор().

  3. Проверка,является ли символ буквой или цифрой, осуществляется с помощью статических методов класса сhar: сhar.isLetter( символ ) и сhar.isDigit( символ ).

  4. При разборе идентификаторов необходимо учитывать максимальную длину имени, а при разборе чисел - переполнение типа int.

  5. Необходимо также реализовать метод инициализации, в который будут вынесены очистка используемых полей, а также вызов метода добавитьКлючевоеСлово() для всех ключевых слов входного языка.

Примеры кода:

Файл LexicalAanlyzer.cs в проекте.

ЛАБОРАТОРНАЯ РАБОТА №4. Таблица имен.

Таблица имен. Реализация элемента таблицы. Общие принципы хранения идентификаторов внутри таблицы, класс System.Collections.Generic.LinkedList<T> . Реализация методов регистрации идентификатора, получения идентификатора из таблицы по имени.

Для задания типа и категории идентификатора логично определить перечисления:

public enum tCat

{

Const, Var, Type

};

public enum tType

{

None, Int, Bool

};

Перечисление tType хранит возможные типы данных исходного языка: void, int, bool; tCat - категории идентификаторов: константа, переменная, тип данных.

Следующим шагом будет объявление структуры Идентификатор. Полями структуры будут имя идентификатора, его тип и категория.

Хранить идентификаторы внутри таблицы имен будем в связанном списке. Реализация класса ТаблицаИмен (NameTable.cs - в проекте) на псевдокоде приведена ниже.

Класс NameTable

{

СвязанныйСписок<Идентификатор> идентификаторы;

Идентификатор добавитьИдентификатор( имя, категория )

{

Идентификатор идентификатор = новый Идентификатор();

идентификатор.имя = имя;

идентификатор.категория = категория;

идентификаторы.добавитьВКонец( идентификатор );

вернуть идентификатор;

}

Идентификатор найтиПоИмени( имя )

{

УзелСвязанногоСписка<Идентификатор> узел = идентификаторы.Первый;

пока( узел != НУЛЛ и узел.Значение.имя != имя )

узел = узел.Следующий;

вернуть узел == НУЛЛ ? новый Идентификатор() : узел.Значение;

}

}

Тестирование работы этого класса можно провести следующим образом: если в процессе лексического анализа кода текущая лексема является идентификатором и её еще нет в таблице идентификаторов, добавляем её в таблицу. Далее получаем список идентификаторов и последовательно выводим их. Псевдокод приведен ниже.

LexicalAnalyzer.инициализировать();

Пока( LexicalAnalyzer.текущаяЛексема != Lexems.конецФайла )

{

если( LexicalAnalyzer.текущаяЛексема == Lexems.Имя

И NameTable.найтиПоИмени( LexicalAnalyzer.текущееИмя ).эквивалентно( новый Идентификатор() ) )

NameTable.добавитьИдентификатор( LexicalAnalyzer.текущееИмя, tCat.Var );

LexicalAnalyzer.разобратьСледующуюЛексему();

}

УзелСвязанногоСписка<Идентификатор> узел = NameTable.получитьИдентификаторы().Первый;

пока( узел != НУЛЛ )

{

Вывести узел.Значение.Имя;

узел = узел.Следующий;

}

Замечания по коду:

  1. В класс ТаблицаИмен необходимо добавить методы для инициализации класса и получения списка всех идентификаторов.

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

Примеры кода:

Файл NameTable.cs в проекте.

ЛАБОРАТОРНАЯ РАБОТА №3.

РАЗРАБОТКА СИНТАКСИЧЕСКОГО АНАЛИЗАТОРА

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]