Для задания типа лексемы обычно используется перечисление, включающее в себя все возможные в исходном языке типы: ключевые слова, операторы, идентификаторы и числа.
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 в проекте.