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

Лабораторная работа № 1.2. Разбор исходного файла.

Методы статического класса System.IO.File для работы с файлами. Чтение, запись текстового файла. Класс System.IO.StreamReader для реализации потокового чтения из файла.

Классы C#, обеспечивающие работу с файловой системой, расположены в пространстве имен System.IO . Поэтому для доступа к этим классам в коде без указания пространства имен необходимо использовать директиву подключения namespace:

using System.IO;

Статический класс File предоставляет основные функции для работы с файлами, в том числе нужные нам чтение файла в строку и запись строки в файл.

Чтение файла в строку. Для выбора исходного файла в структуре файловой системы используется элемент управления OpenFileDialog. Метод ShowDialog() отобразит диалог выбора файла на экран, а о том, какую кнопку в диалоге нажал пользователь можно узнать, сравнив значение, возвращенное методом с константами статического класса DialogResult. При значении, равном DialogResult.OK путь к выбранному файлу в файловой системе можно получить через свойство FileName экземпляра OpenFileDialog. Это значение нужно передать в метод ReadAllText класса File. Метод вернет нам содержимое исходного файла.

Запись строки в файл. Необходимо использовать метод WriteAllText класса File. В качестве параметров ему передаются имя файла и исходная строка. Широко распространенной практикой является проверка на существование файла перед записью с помощью метода Exists класса File.

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

Reader

{

номерСтроки;

позицияСимволаВСтроке;

текущийСимвол;

StreamReader streamReader;

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

{

текущийСимвол = streamReader.Read();

если( текущийСимвол == -1 )

текущийСимвол = конецФайла;

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

{

номерСтроки++;

позицияСимволаВСтроке = 0;

}

иначе если( текущийСимвол == возвратКаретки или текущийСивол == табуляция )

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

иначе

позицияСимволаВСтроке++;

}

инициализировать( путьКФайлу )

{

если файлСуществует( путьКФайлу )

{

streamReader = новый StreamReader( путьКФайлу );

номерСтроки = 1;

позицияСимволаВСтроке = 0;

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

}

}

закрыть()

{

streamReader.close();

}

}

Протестировать работу класса Reader в коде формы можно, например, так:

Reader. инициализировать( путьКФайлу );

Пока ( Reader.текущийСимвол != конецФайла )

{

вывести Reader.номерСтроки;

вывести Reader. позицияСимволаВСтроке;

вывести Reader. текущийСимвол;

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

}

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

  1. Все поля и методы должны быть объявлены как static;

  2. В соответствии с принципами ООП все поля должны быть закрытыми (это замечание касается всех лабораторных работ). Для чтения полей необходимо реализовать методы-свойства. Ни одно из полей НЕ должно записываться вне класса.

  3. Необходимо предусмотреть проблемные ситуации: указан некорректный путь к файлу, многократная инициализация класса (в этом случае необходимо закрывать streamReader перед его пересозданием).

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

Чтение файла в строку.

if(openFileDialog1.ShowDialog() == DialogResult.OK)

{

codeFile = openFileDialog1.FileName.ToString();

string code = System.IO.File.ReadAllText(codeFile);

}

Запись строки в файл.

if(File.Exists(codeFile))

{

File.WriteAllText(codeFile, code);

}

Посимвольное чтение из файла.

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

Лабораторная работа №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 в проекте.

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

Таблица имен. Реализация элемента таблицы. Общие принципы хранения идентификаторов внутри таблицы, класс 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.

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

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