Xreferat.com » Рефераты по информатике и программированию » Программно-методический комплекс для обучения процессу создания компиляторов

Программно-методический комплекс для обучения процессу создания компиляторов

МИНИСТЕРСТВО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ

ВОТКИНСКИЙ ФИЛИАЛ ИЖ Г Т У


Кафедра Организации вычислительных процессов и систем управления


К защите допустить “____”_________ 2003 г


Зав. кафедрой ___________


дипломный проект

Программно-методический комплекс для обучения процессу создания компиляторов

ТЕМА:




РАСЧЕТНО - ПОЯСНИТЕЛЬНАЯ ЗАПИСКА


Выполнил студент группы Д – 1061 _________ А.И. Кузнецов


Руководитель проекта ст. преподаватель _________


Консультант по профессор, д.т.н. _________

охране труда


Консультант по эко- доцент, к.т.н. _________

номической части


Председатель экс- ст. преподаватель _________

пертной комиссии


Воткинск 2003


Определения


В настоящем дипломном проекте применяются следующие термины с соответствующими определениями.

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

БНФ (Бэкуса нормальная форма) – грамматика, состоящая из конечного множества правил, определяющих в совокупности язык программирования.

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

Идентификатор – имя переменной, процедуры, функции, программы.

Инструкция – синтаксическая структура, содержащая ключевые, шумовые слова и конструкции. Бывают простые и структурированные. Простые инструкции не содержат в себе других вложенных инструкций (присваивание, GOTO). Структурированные инструкции могут содержать вложенные инструкции (IF <булево выражение> THEN <безусловный оператор> ELSE <оператор>).

Компилятор – системная программа, выполняющая преобразование программы, написанной на одном алгоритмическом языке, в программу на языке, близком к машинному, и в определенном смысле эквивалентную первой.

Лексема – единица программы, получающаяся в результате лексического анализа, например: for, i, 10, integer, + и т. п.

Лексический анализ – выделение в исходной программе элементарных составляющих: идентификаторов, ограничителей, символов операторов, чисел, ключевых слов, шумовых слов, пробелов, комментариев и т. п.

Литера – любой символ, множество литер составляют лексему.

Литерал – численное или строковое значение, заданное один раз, и не изменяемое в течение программы.

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

Нетерминальный символ – имя конструкции, определенной внутри грамматики.

Рекурсивный спуск – нисходящий метод грамматического разбора, основан на том, что для каждого нетерминального символа, определенного в грамматике, существует отдельная процедура обработки. При этом в процессе своей работы она может вызывать подобные процедуры

Семантика языка программирования - это смысл, который закладывается в каждую конструкцию языка.

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

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

Символьное имя – одно из имен, разрешенных в языке, не являющееся терминальным символом.

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

Синтаксический анализ (грамматический разбор) – формирует синтаксическую единицу – выражение, инструкцию, вызов подпрограммы, декларацию, которые далее обрабатываются семантическим анализатором. Пример структуры: FOR <выражение> TO int DO <body>.

Синтаксический разбор – процесс получения дерева синтаксического разбора на основе заданной грамматики.

Сканер (лексический анализатор) – программа распознавания лексем.

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

Терминальный символ – конечный неделимый элемент конструкции языка, является зарезервированным словом (например READ, (, +).

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


Содержание


Введение Error: Reference source not found

1 Анализ предметной области Error: Reference source not found

1.1 Компиляторы Error: Reference source not found

1.2 Логическая структура компилятора Error: Reference source not found

1.3 Лексический анализ. Сканер Error: Reference source not found

1.4 Синтаксический и семантический анализ Error: Reference source not found

1.5 Грамматики Error: Reference source not found

1.6 Формирование промежуточного кода Error: Reference source not found

Метод четверок Error: Reference source not found

1.7 Обоснование создания учебного комплекса Error: Reference source not found

1.8 Обзор существующих разработок Error: Reference source not found

1.9 Обоснование разработки Error: Reference source not found

2 Создание учебной разработки Error: Reference source not found

2.1 Краткое описание учебного компилятора Error: Reference source not found

2.2 Описание учебного языка Error: Reference source not found

2.3 Лексический анализатор LEXAN Error: Reference source not found

2.3.1 Таблица терминальных символов Error: Reference source not found

2.3.2 Таблица символических имен Error: Reference source not found

2.3.3 Таблица литералов Error: Reference source not found

2.3.4 Работа сканера Error: Reference source not found

2.3.5 Структура листинга Error: Reference source not found

2.3.6 Структура выходного файла Error: Reference source not found

2.3.7 Примерное задание для студента Error: Reference source not found

2.3.8 Описание работы лексического анализатора Error: Reference source not found

2.4 Синтаксический анализатор SinAn Error: Reference source not found

2.4.1 Таблица переходов Error: Reference source not found

2.4.2 Правила работы с таблицей переходов Error: Reference source not found

2.4.3 Правила таблицы переходов для написания программы Error: Reference source not found

2.4.4 Формируемая таблица переходов. Правила заполнения Error: Reference source not found

2.4.5 Правила заполнения формируемой таблицы переходов Error: Reference source not found

2.4.6 Построение деревьев Error: Reference source not found

2.4.7 Семантический анализ Error: Reference source not found

2.5 Формирование промежуточного кода Error: Reference source not found

Циклы Error: Reference source not found

3 Определение трудоемкости по стадиям разработки Error: Reference source not found

3.1 Методика расчета Error: Reference source not found

3.2 Определение затрат на выполнение проекта по стадиям разработки Error: Reference source not found

3.3 Расчет затрат на выполнение проекта по этапам Error: Reference source not found

4 Рекомендации по охране труда при работе с учебным комплексом Error: Reference source not found

Заключение 74

Список использованных источников Error: Reference source not found

Приложения Error: Reference source not found

Введение


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

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

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

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


1 Анализ предметной области


1.1 Компиляторы


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

Автокод очень близок к машинному языку; большинство команд автокода - точное символическое представление команд машины.

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

Компиляторы пишутся как на автокоде, так и на языках высокого уровня. Кроме того, существуют и специальные языки конструирования компиляторов - компиляторы компиляторов.

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

1.2 Логическая структура компилятора


На рисунке 1 представлена структурная схема компилятора.


Программно-методический комплекс для обучения процессу создания компиляторов

Рисунок 1 – Схема компилятора


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

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

Лексический анализатор выполняет распознавание лексем языка и замену их соответствующими кодами. Под лексемами понимаются элементарные единицы, входящие в структуру предложения языка, такие как ключевые слова, константы, имена и т.п. Правильность задания структуры предложения языка на фазе лексического анализа не выполняется. Результатом является поток лексем (кодов – ссылок на таблицы), эквивалентный исходному тексту.

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

Семантический анализ. На этом этапе осуществляется контроль типа и вида всех идентификаторов и других операндов.

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

Оптимизация промежуточного кода – выделение общих подвыражений и вычисление константных подвыражений.

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

Распределение памяти. На этом этапе выделяются конкретные адреса пользователя под переменные, которые генерируются компилятором.

Генератор объектного (ассемблерного) кода – выполняет подстановку кодовых образцов на выходном языке, соответствующих промежуточным кодам программы. Генератору кода могут не требоваться шаблоны, он весь может быть реализован в процедурном виде.

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

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

Программно-методический комплекс для обучения процессу создания компиляторов

Рисунок 2 – Работа с таблицами


Очевидна зависимость структуры компилятора от структуры ЭВМ, точнее, от имеющейся производительности системы. Например, при малой памяти увеличивается количество проходов компиляции (т.н. многопроходные компиляторы), а при наличии памяти большого объема можно все этапы компиляции произвести за один проход (и тогда мы имеем дело с однопроходным компилятором).

Далее мы рассмотрим некоторые из этих составных частей процесса компиляции.


1.3 Лексический анализ. Сканер


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

Распределение по таблицам происходит следующим образом.

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

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

Таким образом, алгоритм работы простейшего сканера можно описать так:

просматривается входной поток символов программы на исходном языке до обнаружения очередного символа, ограничивающего лексему;

для выбранной части входного потока выполняется функция распознавания лексемы;

при успешном распознавании информация о выделенной лексеме заносится в таблицу лексем, и алгоритм возвращается к первому этапу;

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

Работа программы-сканера продолжается до тех пор, пока не будут просмотрены все символы программы на исходном языке из входного потока.

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


Таблица 1 – Таблица терминальных символов

№ п.п. Терминальный символ Комментарий (обозначение)
1 PROGRAM Заголовок программы
2 VAR Описание переменных
3 BEGIN Начало тела программы
4 END Конец тела программы
5 INTEGER Целый тип данных
6 FOR Счетный оператор цикла (для)
7 TO Ключевое слово счетного оператора цикла (до)
8 DO Ключевое слово (выполнить)

Продолжение таблицы 1

№ п.п. Терминальный символ Комментарий (обозначение)
9 WHILE Оператор цикла с предусловием (пока)
10 DIV Деление целочисленное
11 MOD Остаток от целочисленного деления
12 AND Логическое И
13 OR Логическое ИЛИ
14 := Присвоить значение

Сначала заполняется таблица лексем (терминальных символов), затем начинается считывание и обработка входного текста программы пользователя. При работе сканера происходит заполнение таблиц символических имен и литералов.

Данные таблицы могут выглядеть следующим образом:


Таблица 2 – Таблица символических имен

№ п.п. Идентификатор Тип Размер, занимаемый в памяти, байт Относительный адрес в памяти
1 I INTEGER

2 Y REAL

3 X1 REAL






Таблица 3 – Таблица литералов

№ п.п. Литерал Тип Размер, занимаемый в памяти, байт Относительный адрес в памяти
1 1 INTEGER 2 0
2 100 INTEGER 2 2





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

FOR I:=1 TO 100 DO Y:=X1

будет получена строка:

<1,06><2,1><1,14><3,1><1,07><3,2><1,08><2,2><1,14><2,3>,

где в угловых скобках пара чисел задает код таблицы и спецификатор. Можно оформить и в виде таблицы.


Таблица 4 – Таблица выходных символов

№ п.п. 1 2 3 4 5 6 7 8 9 10
Таблица 1 2 1 3 1 3 1 2 1 2
Строка 6 1 14 1 7 2 8 2 14 3

Функционально в сканере могут быть выделены следующие модули[4]:

выделение из входной строки очередного слова;

поиск в таблицах лексем и определение кода лексемы;

формирование и вывод выходной строки.

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

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

В задачи последнего модуля входит занесение в выходную строку кодов лексем.

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

Следующей стадией анализа является синтаксический разбор.

Лексический и синтаксический анализаторы взаимодействуют между собой. Существует два основных способа взаимодействия:

реализуется на основе прямого лексического анализа. От синтаксического анализатора поступает запрос «дать лексему» и указывается тип лексемы;

непрямой лексический анализ. Синтаксический анализатор выдает запрос «дать лексему», тип лексемы не указывается. Нет решающего блока, считаем, что работает группа параллельных автоматов.


1.4 Синтаксический и семантический анализ


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

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

Процесс синтаксического анализа может рассматриваться как построение дерева грамматического разбора для транслируемых предложений. Грамматики могут использоваться как для порождения так и для распознавания предложений языка. Порождение начинается с начального понятия (или аксиомы грамматики). При распознавании с помощью грамматических правил порождается предложение, которое затем сравнивается с входной строкой. При этом применение правил подстановки для порождения очередного символа предложения зависит от результатов сравнения предыдущих символов с соответствующими символами входной строки. Результат анализа исходного предложения в терминах грамматических конструкций удобно представлять в виде дерева. Такие деревья обычно называются деревьями грамматического разбора или синтаксическими деревьями. На рисунке 3 изображено дерево грамматического разбора для предложения READ (VALUE).

Рисунок 3 – Дерево грамматического разбора


Методы грамматического разбора разбиваются на два больших класса восходящие и нисходящие – в соответствии с порядком построения дерева грамматического разбора. Нисходящие (методы сверху-вниз) начинаются с аксиомы грамматики, с корня дерева и пытаются так его наращивать, чтобы последующие узлы дерева соответствовали синтаксису анализируемого выражения. Восходящие (методы снизу-вверх) начинают с элементов предложения (с "листьев") и отыскивают в грамматике какому понятию они соответствуют, т.е. определяют родительскую вершину для этих элементов, и т.д. пока не приходят к корню дерева (аксиоме грамматики). В современных компиляторах применяются как нисходящие, так и восходящие методы.

Достоинством восходящего метода является его несомненная простота, а также высокая скорость выполнения (не тратится время на поиск правила редукции).

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

Поэтому при дальнейшем рассмотрении будет рассматриваться нисходящий разбор, как наиболее пригодный метод при ручном написании компилятора [4].

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

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

В дальнейшем мы будем работать с синтаксически-неориентированными алгоритмами, т.к. будем работать лишь с одним языком – учебный язык на основе языка Паскаль.


1.5 Грамматики


Грамматика языка программирования является формальным описанием его синтаксиса или формы, в которой записаны от­дельные предложения программы или вся программа. Грамматика не описывает семантику или значения различных предло­жений. Информация о семантике содержится в программах ге­нерации объектного кода. В качестве иллюстрации разницы между синтаксисом и семантикой рассмотрим два предложения:

I:=J+K

и

I:=X+Y

где Х и Y являются действительными переменными, a I, J, К — целыми переменными. Эти два предложения имеют оди­наковый синтаксис. Оба являются операторами присваивания, в которых присваиваемое значение определяется выражением, состоящим из двух имен переменных, разделенных оператором сложения. Однако семантика этих двух предложений совершен­но различна. Первое предложение говорит о том, что перемен­ные в выражении должны быть сложены с использованием целых арифметических операций, а результат сложения должен быть присвоен переменной I. Второе предложение задает сло­жение с плавающей точкой, результат которого должен быть преобразован в целое число перед присваиванием. Очевидно, эти два предложения будут скомпилированы в раз­личные последовательности машинных команд, хотя их грамматическое описание одинаково. Различия между ними про­явятся на этапе генерации объектного кода.

На рисунке 4 показаны БНФ грамматики, используемые в дипломном проекте. Подчеркнутые волнистой линией элементы могут опускаться (не использоваться).


<prog> ::= PROGRAM <prog-name> VAR <dec-list> BEGIN <stmt-list> END.

<prog-name> ::= id ;

<dec-list> ::= <dec> { ; <dec> } ;

<dec> ::= <id-list> : <type>

<type> ::= INTEGER | REAL | STRING

<id-list> ::= id { , id }

<stmt-list> ::= <stmt> { ; <stmt> } ;

<stmt> ::= <assign> | <for> | <read> | <write> | <while> | <repeat> | <if>

<assign> ::= id := <exp>

<exp> ::= – <term> { + <term> | – <term> }

<term> ::= <factor> { * <factor> | DIV <factor> | / <factor> }

<factor> ::= id | int | real | <text-val> | (<exp>)

<read> ::= READ ( <id-list> )

<write> ::= WRITE ( <value> { , <value>} )

<for> ::= FOR <index-exp> DO <body>

<index-exp> ::= id := <exp> TO|DOWNTO <exp>

<body> ::= <stmt> | BEGIN <stmt-list> END

<value> ::= <id-list> | <text-val>

<text-val> ::= ′ <text> ′

<text> ::= string

<if> ::= IF

Если Вам нужна помощь с академической работой (курсовая, контрольная, диплом, реферат и т.д.), обратитесь к нашим специалистам. Более 90000 специалистов готовы Вам помочь.
Бесплатные корректировки и доработки. Бесплатная оценка стоимости работы.

Поможем написать работу на аналогичную тему

Получить выполненную работу или консультацию специалиста по вашему учебному проекту

Похожие рефераты: