Приём заказов:
Круглосуточно
Москва
ул. Никольская, д. 10.
Ежедневно 8:00–20:00
Звонок бесплатный

Принципы разработки компиляторов

Диплом777
Email: info@diplom777.ru
Phone: +7 (800) 707-84-52
Url:
Логотип сайта компании Диплом777
Никольская 10
Москва, RU 109012
Содержание

ВВЕДЕНИЕ

компилятор программа грамматика

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

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

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

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

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

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

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

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

В качестве среды разработка для реализации программы использован язык программирования C++ и среда программирования Visual Studio C++ 2012.

1. ОПИСАНИЕ ВХОДНОГО ЯЗЫКА

Входной язык представляет собой подмножество языка программирования Pascal.

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

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

Предусмотрены следующие варианты операторов входной программы:

– оператор присваивания (:=);

– зарезервированные слова If, Else, Then, While, Do, Prog, End;

– арифметические операции (+, -, /, *);

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

– все идентификаторы должны восприниматься как переменные;

– допускается присутствие комментариев оформленных виде: //комментарий

Для выделения лексем заранее строится конечный автомат.

Данный язык относится к КС-языкам, поэтому может быть описан следующей грамматикой:

<буква>>”A” |….| ”Z” |….| ”a” |….| ”z” |”_”

<арифм.опер.>>”+” | ”-” | ”*” |”/”

<цифра>>”0|”1”|”2”|”3”|”4”|”5”|”6”|”7”|”8”|”9”

< ID >><буква>

|<ID><буква>

|<ID><цифра>

<симв.конст.> >'<буква>’

|'<цифра>’

<операнд>><ID>

|< симв.конст.>

<арифм.выр.>> <операнд><арифм.оп.><операнд>

|<арифм.выр><арифм.оп.><операнд>

|<операнд><арифм.оп.>< арифм.выр >

|<операнд><арифм.выр.><операнд>

<оператор>><оп.цикла>

|< оп.присв>

|<услов.оп>

<оп.присв.>><ID>”:=”<операнд>”;”

|<ID>”:=”<арифм.выр.>”;”

<блок опер.> ><оператор> ”;” <оператор>

|<блок>”;”<оператор>

<тело>>”{“<блок опер>”;}”

<оп.цикла>> “do<тело>while ”(” <арифм.выр.>”)” ”;”

|“do””{“ <оператор> ”}” “while””(” <арифм.выр.>”)””;”

<услов.оп>> if “(”<арифм.выр>“)””then”<тело>”else”<тело>

|if “(” <арифм.выр>“)””then”<тело>

|if “(”<арифм.выр>“)”then”<оператор>”else”<оператор>

|if “(” <арифм.выр>“)””then”<оператор>

|if “(”<арифм.выр>“)””then”<оператор>”else”<тело>

|if “(”<арифм.выр>“)””then”<тело>”else”<оператор>

<прогр.>> “prog”<тело> “end

|“prog”<оператор> “end

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

2. ОРГАНИЗАЦИЯ ТАБЛИЦЫ ИДЕНТИФИКАТОРОВ

2.1 Назначение таблицы идентификаторов

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

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

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

2.2 Метод упорядоченного списка

Этот метод является простым методом построения таблиц идентификаторов. Элементы записываются в таблицу в порядке возрастания. Так как упорядочивание таблицы идентификаторов происходит на всех этапах обращения к таблице, то для ее построения можно пользоваться только алгоритмом прямого упорядоченного включения элементов. При добавлении нового элемента в таблицу идентификаторов он сначала добавляется в конец таблицы, а затем идет переупорядочивание элементов таблицы идентификаторов. Эффективным методом для поиска элементов является логарифмический поиск, на каждом шаге которого, число элементов, которые могут содержать искомый элемент, сокращается в два раза. Максимально число сравнений при поиске 1+log2(N).

Схема алгоритма добавления идентификатора представлена на рис. 1

Рисунок 1 – Алгоритм добавления идентификатора

Схема алгоритма бинарного поиска идентификатора представлена на рис. 2

Рисунок 2 – Алгоритм поиска идентификатора

2.3 Результат выполнения программы

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

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

3. ПРОЕКТИРОВАНИЕ ЛЕКСИЧЕСКОГО АНАЛИЗАТОРА

3.1 Назначение лексического анализатора

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

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

3.2 Граф переходов лексического анализатора

Распознаватель лексем языка для данной грамматики задан конечным детерминированным автоматом, схема которого представлена на рисунках 3, 4 и 5.

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

Рисунок 4 – Схема распознавателя 2

Рисунок 5 – Схема распознавателя 3

Легенда:

V – любой определенный алфавитно-цифровой символ (буквы латинского алфавита, знак «_», десятичные цифры);

V(*) – любой символ кроме перечисленных в скобках;

B – буквы латинского алфавита и знак «_»;

B(*) – любая буква кроме перечисленных в скобках;

Р – пробел, табуляция, перенос строки;

D – недопустимые символы (все кроме перечисленных);

F – сохранение (ID – в таблице идентификаторов; L -в таблице лексем);

e – ошибка;

s – имя лексемы;

Состояния соответствуют:

Н – начальное состояние;

К – конечное состояние;

P1, P2, P3, P4 – состояния, соответствующие ключевому слову “prog”;

En1, En2 – состояния, соответствующие ключевому слову “end”;

I1, I2 – состояния, соответствующие ключевому слову “if”;

E1, E2, E3, E4 – состояния, соответствующие слову “else”;

T1, T2, T3, T4 – состояния, соответствующие слову “then”;

W1, W2, W3, W4, W5 – состояния, соответствующие ключевому слову “while”;

D1, D2 – состояния, соответствующие ключевому слову “do”;

S1, S2, S3 – состояния, соответствующие символьное константе:

A1, A2 – состояния, соответствующие оператору присваивания “:=”;

С1, С2 – комментарий;

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

3.3 Результат выполнения программы

Результат разбора входных выражений на лексемы представлен на рисунке 6.

Рисунок 6 – Результат работы лексического анализатора (таблица лексем)

Спроектированный лексический анализатор выполняет лексический анализ входного текста в соответствии с заданной грамматикой и порождает таблицу лексем с указанием их типов. Программа выводит также сообщения о наличие во входном тексте ошибок. Этот алгоритм послужит в дальнейшем базой для построения дерева вывода в 3 части курсовой работы.

4. ПОСТРОЕНИЕ СИТАКСИЧЕСКОГО АНАЛИЗАТОРА

4.1 Дерево вывода

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

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

Длину идентификаторов и строковых констант считать ограниченной 32 символами.

4.2 Синтаксический анализатор

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

Программирование работы недетерминированного МП-автомата – это сложная задача. Разработанный алгоритм, позволяет для произвольной КС-грамматики определить, принадлежит ли ей заданная входная цепочка (алгоритм Кока-Янгера-Касами).

Доказано, что время работы этого алгоритма пропорционально n3, где n – длина входной цепочки. Для однозначной КС-грамматики при использовании другого алгоритма (алгоритм Эрли) это время пропорционально n2. Подобная зависимость делает эти алгоритмы требовательными к вычислительным ресурсам. На практике и не требуется анализ цепочки произвольного КС-языка – большинство конструкций языков программирования может быть отнесено в один из классов КС-языков, для которых разработаны алгоритмы разбора, линейно зависящие от длины входной цепочки.

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

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

– простого предшествования;

– расширенного предшествования;

– слабого предшествования;

– смешанной стратегии предшествования;

– операторного предшествования.

Алгоритм построения синтаксического анализатора включает следующие этапы:

1) составление правил грамматики языка;

2) выявление множества крайних правых и кайних левых терминальных и нетерминальных символов;

3) построение матрицы предшествования.

Рассмотрим эти этапы более подробно.

4.3 Таблицы предшествования

Множество правил грамматики имеет вид:

<буква>>”A” |….| ”Z” |….| ”a” |….| ”z” |”_”

<арифм.опер.>>”+” | ”-” | ”*” |”/”

<цифра>>”0”|”1”|”2”|”3”|”4”|”5”|”6”|”7”|”8”|”9”

< ID >><буква>

|<ID><буква>

|<ID><цифра>

<симв.конст.> >'<буква>’

|'<цифра>’

<операнд>><ID>

|< симв.конст.>

<арифм.выр.>> <операнд><арифм.оп.><операнд>

|<арифм.выр><арифм.оп.><операнд>

|<операнд><арифм.оп.>< арифм.выр >

<оператор>><оп.цикла>

|< оп.присв>

|<услов.оп>

<оп.присв.>><ID>”:=”<операнд>”;”

|<ID>”:=”<арифм.выр.>”;”

<блок опер.> ><оператор> ”;” <оператор>

|<блок>”;”<оператор>

<тело>>”{“<блок опер>”;}”

<оп.цикла>> “do”<тело>“while” ”(” <арифм.выр.>”)” ”;”

|“do””{“ <оператор> ”}” “while””(” <арифм.выр.>”)””;”

<услов.оп>> if “(”<арифм.выр>“)””then”<тело>”else”<тело>

|if “(” <арифм.выр>“)””then”<тело>

|if “(”<арифм.выр>“)”then”<оператор>”else”<оператор>

|if “(” <арифм.выр>“)””then”<оператор>

|if “(”<арифм.выр>“)””then”<оператор>”else”<тело>

|if “(”<арифм.выр>“)””then”<тело>”else”<оператор>

<прогр.>> “prog”<тело> “end

|“prog”<оператор> “end

Грамматика является грамматикой операторного предшествования, так как она не содержит -правил и правые части правил не содержат смежных нетерминальных символов. Построим множества крайних левых и крайних правых символов L(U), R(U) относительно всех нетерминальных символов грамматики.

Таблица 3.1 – Множества крайних правых и крайних левых символов

Символ (U)

Начало построения

L(U)

R(U)

<элемент>

<число>,ID, <элемент>

<число>,ID

<лев.выр>

<элемент>,<лев.выр>

<элемент>,<число>

<выр>

<лев.выр>

”;”

<сис.уравн>

<сис.уравн>,<выр>

<выр>

На основе полученных множеств построим множества крайних левых и крайних правых терминальных символов Lt(U), Rt(U) относительно всех нетерминальных символов грамматики.

Таблица 3.2 – Множества крайних правых и крайних левых терминальных символов

Символ (U)

Начало построения

L(U)

R(U)

<элемент>

<число>,ID

<число>,ID

<лев.выр>

<число>,ID

<число>,ID

<выр>

<число>,ID

”;”

<сис.уравн>

<число>,ID

”;”

На основе этих множеств и правил грамматики G построим матрицу предшествования грамматики:

Таблица 3.3 – Матрица предшествования исходной грамматики

константа

переменная.

;

=

+

*

/

Константа

<

<

<

<

<

Переменная

<

<

<

<

<

;

<

<

=

<

<

<

+

<

<

*

<

<

/

<

<

На основе матрицы предшествования производится синтаксический анализ методом “сдвиг-свертка” в результате которого формируется матрица коэффициентов для дальнейшего решения методом Гаусса.

5. ГЕНЕРАЦИЯ КОДА

Генерация объектного кода — это перевод компилятором внутреннего представления исходной программы в цепочку символов выходного языка.

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

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

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

5.1 Общие принципы генерации кода

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

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

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

5.2 Основные методы оптимизации

Задача оптимизации кода состоит в создании эффективного (с точки зрения размера памяти и времени выполнения) целевого кода. Желаемая степень оптимизации будет зависеть от обстоятельств. Иногда она не нужна, например, если у программы малое время выполнения, умеренные запросы к памяти и, возможно, малый срок жизни.

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

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

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

6. ОПИСАНИЕ ПРОГРАММЫ

#include “stdafx.h”

//Подключаем необходимые заголовочные файлы

#include <iostream>

#include <string>

#include <conio.h>

///////////////////

#include “states.h” //функции переходов автомата

#include “common.h” //вспомогательные функции

///////////////////

//по умолчанию используем пространство имен “std”

using namespace std;

//таким образом делаем переменные видимыми в разных модулях

//extern lexem* idtable[MAXHASH]; //таблица идентификаторов

extern lexem** idtable = NULL;//таблица идентификаторов

extern lexem* lexTableHead = NULL; //указатель на начало (начальный елемент) таблицы лексем

extern lexem* lexTableEnd = NULL; //указатель на конец (последний елемент) таблицы лексем

int row = 0;

int col = 0;

//”главная” функция

int _tmain(int argc, _TCHAR* argv[])

{

setlocale( LC_ALL,”Russian” ); //данная строчка необходима для корректного отображения кириллицы

header(); //выводим “шапку”

string fileName = “c:/test.txt”;

//задаем имя файла

//cout << “Введите путь и имя файла n”;

//cin >> fileName;

//считаем содерживое файла (текст программы) в строку

string programText = readFile(fileName);

initIdTable();

string lexem = “”; //переменная для хранения имени лексемы

STATE currState = sBEGIN; //текущее состояние автомата

//текс программы разберем посимвольно в цикле

for(unsigned int i = 0; i < programText.length(); i++){

char c = toupper(programText[i]); //текущий символ

if(c == ‘n’)

{

row++;

col = 0;

}

switch(currState){

case sBEGIN:

lexem.clear();

currState = beginState(c,lexem);

break;

/////////////////////////////////////////

case sIF1:

currState = if1State(c,lexem);

break;

/////////////////////////////////////////

case sIF2:

currState = if2State(c,lexem);

break;

/////////////////////////////////////////

case sELSE1:

currState = else1State(c,lexem);

break;

/////////////////////////////////////////

case sELSE2:

currState = else2State(c,lexem);

break;

/////////////////////////////////////////

case sELSE3:

currState = else3State(c,lexem);

break;

/////////////////////////////////////////

case sELSE4:

currState = else4State(c,lexem);

break;

/////////////////////////////////////////

case sFOR1:

currState = for1State(c,lexem);

break;

/////////////////////////////////////////

case sFOR2:

currState = for2State(c,lexem);

break;

/////////////////////////////////////////

case sFOR3:

currState = for3State(c,lexem);

break;

/////////////////////////////////////////

case sDO1:

currState = do1State(c,lexem);

break;

/////////////////////////////////////////

case sDO2:

currState = do2State(c,lexem);

break;

/////////////////////////////////////////

case sPROG1:

currState = prog1State(c,lexem);

break;

/////////////////////////////////////////

case sPROG2:

currState = prog2State(c,lexem);

break;

/////////////////////////////////////////

case sPROG3:

currState = prog3State(c,lexem);

break;

/////////////////////////////////////////

case sPROG4:

currState = prog4State(c,lexem);

break;

/////////////////////////////////////////

case sEND1:

currState = end1State(c,lexem);

break;

/////////////////////////////////////////

case sEND2:

currState = end2State(c,lexem);

break;

/////////////////////////////////////////

case sSYMBOL1:

lexem = “‘”;

currState = symbol1State(c,lexem);

break;

/////////////////////////////////////////

case sSYMBOL2:

currState = symbol2State(c,lexem);

break;

/////////////////////////////////////////

case sSYMBOL3:

currState = symbol3State(c,lexem);

break;

/////////////////////////////////////////

case sASSIGN1:

lexem = “:”;

currState = assign1State(c,lexem);

break;

/////////////////////////////////////////

case sASSIGN2:

lexem = “”;

currState = assign2State(c,lexem);

break;

/////////////////////////////////////////

case sCOMMENT1:

lexem = “”;

currState = comment1State(c,lexem);

break;

/////////////////////////////////////////

case sCOMMENT2:

currState = comment2State(c,lexem);

break;

/////////////////////////////////////////

case sIDENT:

currState = idState(c,lexem);

break;

case sNUMBER:

/////////////////////////////////////////

currState = numberState(c,lexem);

break;

}

lexem += c;

col++;

}

//сохраняем таблицы

saveIdentTable();

saveLexTable();

//освободим ресурсы (удалим содержимое таблиц)

clearIdentTable();

clearLexTable();

wcout << endl << L”Для завершения программы нажмите любую клавишу…”;

_getch();//”задержка”

return 0;

}

ЗАКЛЮЧЕНИЕ

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

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

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

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

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

СПИСОК ЛИТЕРАТУРЫ

1. Гордеев А.В. Молчанов Л.Ю. Системное программное обеспечение, – СПб.: Питер. 2002. – 734с.

2. Кампапиец Р.II. Манькоп Е.В., Филатов Н.Е. Системное программирование. Основы построения трансляторов: Учеб. пособие для высших и средних учебных заведений. – СПб.: КОРОНА Принт, 2000. -256 с.

3. Гордеев А.В. Операционные системы: Учебник для вузов.

2-е изд.-СПб.: Питер, 2004. – 416 с.

4. Олифер В.Г., Олифер Н.А. Сетевые операционные системы. – СПб.: Питер. 2002. – 544 с.

5. Брайан Оверленд C++ без страха,- СПб.: Питер. 2005. – 432с.

6. Марченко А.Л. C++ Бархатный путь,- СПб.: Питер. 2005. – 401с.

Леонид Федотов
Леонид Федотов
Окончил НИУ ВШЭ факультет компьютерных наук. Сам являюсь кандидатом наук. По специальности работаю 13 лет, за это время создал 8 научных статей и 2 диссертации. В компании подрабатываю в свободное от работы время уже более 5 лет. Нравится помогать школьникам и студентам в решении контрольных работ и написании курсовых проектов. Люблю свою профессию за то, что это направление с каждым годом становится все более востребованным и актуальным.
Поделиться курсовой работой:
Поделиться в telegram
Поделиться в whatsapp
Поделиться в skype
Поделиться в vk
Поделиться в odnoklassniki
Поделиться в facebook
Поделиться в twitter
Похожие статьи
Раздаточный материал для дипломной работы образец

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

Читать полностью ➜
Задание на дипломную работу образец заполнения

Дипломная — это своеобразная заключительная работа, которая демонстрирует все приобретенные студентом знания во время обучения в определенном вузе. В зависимости от специализации к исследовательским работам

Читать полностью ➜