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

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

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 в проекте.



Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: