Методы синтаксического анализа на базе регулярных выражений

 

Содержание


Введение

.Разработка технического задания на проектирование

1.1Общая постановка задачи

1.2Определение требований к программе

.3Предварительный выбор метода решения задачи

1.3.1Предварительный выбор метода решения тестовой задачи

1.3.2Предварительный выбор метода решения синтаксического анализатора

1.4Определение требований к системе

1.4.1Требования к системе

.4.2Требования к техническому обеспечению

.4.3Требования к программному обеспечению

2.Проектирование программного приложения

2.1Разработка технологии обработки информации

2.2Разработка структуры и формы представления данных

.3Разработка алгоритма решения задачи

.4Разработка программы решения задачи

2.4.1Выбор инструментальных средств

2.4.2 Разработка структуры программы

.4.3 Проектирование программных модулей

.4.4 Проектирование программного интерфейса

2.5Определение конфигурации технических средств

2.6Тестирование программы

2.6.1 Общие сведения

2.6.2 Процесс тестирования программы

.6.3 Оценка надежности программы

Заключение

Список используемой литературы

Приложение А. Г, Д, Е

Введение


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

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

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

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


1.Разработка технического задания на проектирование


.1Общая постановка задачи


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


1.2Определение требований к программе


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

-использование одной библиотеки iostream;

-описание переменных типов char*, int, одномерных массивов типа int;

-следующие арифметические и логические выражения:+(сложение), ++(инкрементирование), !=(проверка на неравенства), ==(эквиваленция), =(присваивание), ||(логическое ИЛИ), <<(сдвиг), -(субстракция);

-следующиеоператоры: for, while, do..while, system, if, if..goto;

-функции ввода и вывода: printf;

-одно строчные и многострочные комментарии.

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


1.3Предварительный выбор метода решения задачи


.3.1Предварительный выбор метода решения тестовой задачи

ДНФ, для которой выполняются свойства совершенства, называется совершенной ДНФ (СДНФ).

Перечислим их:

1.Каждое логическое слагаемое формулы содержит все переменные, входящие в функцию;

.Все логические слагаемые различны;

.Ни одно слагаемое не содержит одновременно переменную и ее отрицание;

.Ни одно слагаемое не содержит одну и ту же переменную дважды.

При выполнении данных требования функция будет являться СДНФ.


1.3.2Предварительный выбор метода решения синтаксического анализатора

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


.4Определение требований к системе


.4.1Требования к системе в целом

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

1.4.2Требования к техническому обеспечению


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

1) Windows. Процессор с архитектурой i386 <#"justify">3) MacOSX.iMacс MacOS10.6 SnowLeopardи выше с библиотеками необходимыми для запуска программы


1.4.3Требования к программному обеспечению

1) Linux: Debian 3.1(Sarge), Mandriva 2007, Ubuntu 7.04 и выше, библиотеки: libqtcore, libqtgui, х11.

)WindowsХР SР 2 и выше, библиотеки libqtcore, libqtgui.

3)MacOSX 10.6 и выше, библиотеки libqtcore, libqtgui.


2.Проектирование программного приложения


2.1Разработка технологии обработки информации


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

-создание файла;

- заполнение его данными;

-размещение на носителе;

-доступном для программы;

-разрешение доступа для чтения.

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

Структура технологического процесса обработки информации должна быть следующая:

-считать файл;

-удалить из файла все комментарии (сначала много строчные, затем одно строчные);

-построчно разделить оставшийся текст в массив строк;

-произвести синтаксический анализ каждой строки на различные операторы;

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


2.2Разработка структуры и формы представления данных


Необходимо использование соглашений:

-объявление переменных типа char* должно сопровождаться обязательной инициализацией строкой переменной; переменных типа int может сопровождаться необязательной инициализацией, но при этом все переменные должны объявляться отдельно; массивы типа int не могут сопровождаться инициализацией, при этом могут быть только одномерными;

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

-для ключевых слов используют строчные буквы, для идентификаторов - строчные и прописные;

-в одной строке не могут размещаться несколько операторов, и не допускается разделение одного оператора на несколько строк; каждый оператор должен сопровождаться «;»;

-комментарии не могут располагаться внутри других многострочных комментариев;

-функции вывода отображают или текст, или данные.

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

Отформатированные исходные данные будут выведены в стандартный поток вывода. Синтаксические ошибки исходных данных будут выведены в стандартный поток ошибок с выводом сообщения о каждой найденной ошибке.


2.3Разработка алгоритма решения задачи


Алгоритм решения задачи, построен по правилам определенным ГОСТ 19.701-90, ГОСТ 19.002-80, ГОСТ 19.003-80 выполнен в приложении 1 на рисунке 1.

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

Весь алгоритм разделен на несколько частей:

  • Открытие и чтение файла
  • Удаление коментариев
  • Построчный разбор
  • Разбор логических и алгебраических выражений
  • Вывод отформатированного кода

2.4Разработка программы решения задачи


2.4.1Выбор инструментальных средств

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

Особенность которая отличает Qtот других библиотек, это использование Meta Object Compiler (МОС) - система для предварительной обработки исходного кода. МОС позволяет увеличить производительность библиотек, добавляя такие понятия как слоты и сигналы. Так же позволяет делать код более лаконичным. МОС ищет в заголовочных файлах С++ описания классов, содержащие макрос Q_OBJECT и создает дополнительный исходный файл на С++ содержащий мета-объектный код.

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

Так же можно отметить что исходный код а так же библиотеки разрабатываемые сообществом в свободном доступе в Git-хранилище

проектирование конфигурация тестирование синтаксический анализатор

2.4.2 Разработка структуры программы

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

-первая часть будет считывать файл

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

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

-Четвертая часть программы будет обрабатывать поступившие данные как логические, алгебраические операции, переменные, элементы массивов, числа или строки;

-Пятая часть будет выводить текст программы в соответствии с правилами форматирования.


2.4.3Проектирование программных модулей

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

-для второй как имя файла, который следует считать и обработать;

-для третьей и пятой частей программы как значимая часть данных, содержащихся в этом файле;

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


2.4.4Проектирование программного интерфейса

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

-запуск программы, выбор файла для анализа;

-обработка информации, находящейся в этом файле;

-вывод оригинального кода;

-вывод форматированного кода;

Вывод первой найденной синтаксической ошибки с указанием номера строки

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

  • нарушен баланс /* */
  • код за пределами функции майн
  • кейс за пределами свитча
  • Использование необъявленной переменой
  • Использование необъявленной массива
  • отсутствие требуемого выражения

.5Определение конфигурации технических средств


.6Тестирование программы


2.6.3Общие сведения

Контрольными данными является файл с информацией, который обладает следующими свойствами:

-использование одной библиотеки iostream;

-описание переменных типов char*, int, одномерных массивов типа int;

-следующие арифметические и логические выражения:+ (сложение), ++ (инкрементирование), != (проверка на неравенства), == (эквиваленция), = (присваивание), || (логическое ИЛИ), << (сдвиг), - (субстракция).

-Следующиеоператоры: for, while, do..while, system, if;

-функции ввода и вывода: printf;

-одно строчные и многострочные комментарии;

-объявление переменных типа char* должно сопровождаться обязательной инициализацией строкой переменной; переменных типа int может сопровождаться необязательной инициализацией, но при этом все переменные должны объявляться отдельно; массивы типа int не могут сопровождаться инициализацией, при этом могут быть только одномерными;

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

-для ключевых слов используют строчные буквы, для идентификаторов - строчные и прописные;

-в одной строке не могут размещаться несколько операторов, и не допускается разделение одного оператора на несколько строк; каждый оператор должен сопровождаться «;»;

-комментарии не могут располагаться внутри других многострочных комментариев;

-функции вывода отображают или текст, или данные.

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


2.6.4Процесс тестирования программы

Для тестирования программы можно применить следующий алгоритм:

-создать файл на носителе;

-заполнить его данными;

-запустить программу, указав путь к файлу;

-подождать вывода программы;

-сравнить полученные данные с теоретическими.

2.6.5Оценка надежности

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


Заключение


В данном курсовом проекте рассматривалось технологии создания синтаксического анализатора на языке C++\Qt. Область применения синтаксических анализаторов очень широка: компиляторы программного кода, интерпретаторы, системы решения математических выражений, проверка орфографии национальных алфавитов.

Использование языка С++\Qt расширяет программно-аппаратную совместимость со всем спектров современных компьютеров, части смартфонов , UMPC и встроенных систем.

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


Список использованных источников


1. Разработка компиляторов [Электронный ресурс]/ Н.Н Волконская, А.Е Москаль, Д.Ю. Булычев, А.А. Терехов.- Интернет-Университет Информационнных технологий. - Электрон. текст. дан. - Режим доступа http//www.INYUIT.ru, свободный.

. Теория и реализация языков программирования [Электронный ресурс]/ В.А. Серебряков, М.П. Галочкин, Д.Р. Гончар, М.Г.Фуругян.-Интернет Университет Информационныхъ технологий - Электрон. текст. дан. - Режим доступа http//www.INYUIT.ru, свободный.

. Язык программирования С++ [Электронный ресурс]/ А.Л. Фридман.- Университет Информационнных технологий. - Электрон. текст. дан. - Режим доступа http//www.INYUIT.ru, свободный.

. Основы программирования на языке С [Электронный ресурс]/ Н.А. Калинин, Н.А. Костюкова. - Интернет-Университет Информационнных технологий. - Электрон. текст. дан. - Режим доступа http//www.INYUIT.ru, свободный.


Приложение А


/*


*/

#include <iostream>main()

{

char *formula="a&b|!a&b";

int result;

result=0;

int count;

count=1;

int u;

u=0;

int vars[255];

for (int i=0;i<255;i=i+1)

vars[i]=0;

d:

if (formula[u]=='&')

count=count+1;

if (formula[u]!='&' && formula[u]!='|' && formula[u]!='\0' && formula[u]!='!')

{

int var;

var=formula[u];

if (vars[var]!=0)

result=1;

if (vars[var]==0)

vars[var]=1;

}

u=u+1;

if (formula[u]!='|' && formula[u]!='\0')

goto d;

int tablesize;

tablesize=1<<count;

int table[tablesize];

for (int i=0;i<tablesize;i=i+1)

table[i]=0;

//

// проверка всей функции на неповторения минтерм

//

int minterm;

minterm=0;

int table2[255];

for (int j=0;j<255;j=j+1)

table2[j]=vars[j];

for (int i=0;formula[i];i=i+1)

{

if (formula[i]=='|')

{

if (table[minterm]!=0)

result=2;

if (table[minterm]==0)

table[minterm]=1;

int h;

h=0;

for (int j=0;j<255;j=j+1)

if (table2[j]==1)

h=1;

if (h==1)

result=3;

minterm=0;

for (int j=0;j<255;j=j+1)

table2[j]=vars[j];

}

if ( i==0 && formula[0]!='!')

{

int push;

push=0;

int var;

var=formula[i];

if (table2[var]!=1)

result=1;

if (table2[var]==1)

table2[var]=0;

for (int j=0;j<var;j=j+1)

if (vars[j]==1)

push=push+1;

int t;

t=1<<push;

minterm=minterm+t;

}

if ( i==0 && formula[0]=='!')

{

int var;

var=formula[i+1];

if (table2[var]!=1)

result=1;

if (table2[var]==1)

table2[var]=0;

}

if (formula[i]=='&' || formula[i]=='|')

{

if ( formula[i+1]!='!')

{

int push;

push=0;

int var;

var=formula[i+1];

if (table2[var]!=1)

result=0;

if (table2[var]==1)

table2[var]=0;

for (int j=0;j<var;j=j+1)

if (vars[j]==1)

push=push+1;

int t;

t=1<<push;

minterm=minterm+t;

}

if ( formula[i+1]=='!')

{

int var;

var=formula[i+2];

if (table2[var]!=1)

result=0;

if (table2[var]==1)

table2[var]=0;

}

}

}

if (table[minterm]!=0)

result=2;

if (table[minterm]==0)

table[minterm]=1;

int h;

h=0;

for (int j=0;j<255;j=j+1)

if (table2[j]==1)

h=1;

if (h==1)

result=3;

switch (result)

{

case 0:

printf ("ok");

break;

case 1:

printf ("vars not uniq in one minterm");

break;

case 2:

printf ("minterms not uniq");

break;

case 3:

printf ("wront vars count in one minterm");

break;

}

system ("pause");

}

Приложение Г, Д, Е


#ifndef MAINWINDOW_H

#define MAINWINDOW_H

#include <QtGui>MainWindow : public QMainWindow

{

Q_OBJECT:

MainWindow(QWidget *parent = 0);

~MainWindow();:

QTextEdit *source;

QTextEdit *formated;

QTextEdit *errors;

void ParseFile();

int ParseCode(QString code);

void ParseExpr(QString expr);

void ShowCode(QString code);

int result;

QStringList vars,arrays,labels,nlabels;slots:

void fileOpen();

};

#endif // MAINWINDOW_H

#include "mainwindow.h"::MainWindow(QWidget *parent)

: QMainWindow(parent)

{

QWidget *wgt=new QWidget(this);

setCentralWidget(wgt);

QVBoxLayout *lay1=new QVBoxLayout(wgt);

QHBoxLayout *lay2=new QHBoxLayout();

source=new QTextEdit(this);

formated=new QTextEdit(this);

errors=new QTextEdit(this);

lay1->addLayout(lay2);

lay2->addWidget(source);

lay2->addWidget(formated);

lay1->addWidget(errors);

errors->setMaximumHeight(60);

this->menuBar()->addAction("Open",this,SLOT(fileOpen()));

}MainWindow::fileOpen()

{

QString file= QFileDialog::getOpenFileName(this,"Source code","","*.cpp",new QString("*.cpp"));

if (file!=""){

QFile fl(file);

fl.open(QFile::Text|QFile::ReadOnly);

source->setText(fl.readAll());

fl.close();

ParseFile();

}

}MainWindow::ParseFile(){::setCodecForTr(QTextCodec::codecForName("UTF8"));

result=0;pre_code1=source->toPlainText(),code="";

bool rem=false,rem2=false;

for (int i = 0; i < pre_code1.length() - 1; i++)

{

if (!rem2)

{

if (pre_code1[i] == '/' && pre_code1[i+1] == '*') rem = true;

if (i>2)

if (pre_code1[i-2] == '*' && pre_code1[i-1] == '/') rem = false;

}

if (!rem)

{

if (pre_code1[i]=='/' && pre_code1[i+1]=='/')rem2=true;

if (pre_code1[i]=='\n')rem2=false;

}

if ((rem||rem2)==false) code += pre_code1[i];

}line_nmb;(code.indexOf("/*")!=-1)result=1;

line_nmb=ParseCode(code);err=QStringList()<<tr("Код верный")<<tr("%1:нарушен баланс /* */").arg(line_nmb)<<tr("%1:код за пределами функции майн").arg(line_nmb)<<("%1:нарушен баланс { }").arg(line_nmb)<<tr("%1:отсуствие требуемого выражения").arg(line_nmb)<<

tr("прыжок к необявленой метке")<<tr("%1:Использование необьявленой переменой").arg(line_nmb)<<tr("%1:Использование необьявленого масива").arg(line_nmb)<<("%1:кейс за пределами свитча").arg(line_nmb)<<tr("%1:Нераспознаная строка").arg(line_nmb)<<tr("%1:Нераспознаное выражение").arg(line_nmb);

errors->setText(err[result]);(code);

}MainWindow::ParseCode(QString qwer){

int line_nmb=0;code=qwer.split("\n");

int incl = 0,swit_ch = 0,need_op=0;

bool mained=false,included=false;(QString line,code){=line.trimmed();_nmb++;(line=="")continue;(swit_ch==1)

if (line == "{")

{

incl++;

swit_ch = 2;

continue;

}(swit_ch == 2)

if (line == "}")

{

incl--;

swit_ch = 0;

continue;

}(incl==0){

if (line=="#include <iostream>"){included=true;continue;}

if (line=="int main()"){mained=true;continue;}

}(line=="{"){

incl++;

need_op=0;

continue;

}(line=="}"){

incl--;


if (incl<0)result=3;


}else{

if (!(included&&mained)){

result=2;

}*reg;=new QRegExp("^for \\((.*);(.*);(.*)\\)$");(reg->indexIn(line)>-1){

ParseExpr(reg->cap(1));

ParseExpr(reg->cap(2));

ParseExpr(reg->cap(3));

need_op=1;

}else{

reg=new QRegExp("^if \\((.*)\\)$");

if (reg->indexIn(line)>-1){

ParseExpr(reg->cap(1));

need_op=1;

}else{

need_op=0;

reg=new QRegExp("^switch \\((.*)\\)$");

if (reg->indexIn(line)>-1){

swit_ch = 1;

ParseExpr(reg->cap(1));

}else{

reg=new QRegExp("^case (.*):$");

if (reg->indexIn(line)>-1){

if (swit_ch!= 2)result=8;

ParseExpr(reg->cap(1));

}else{

reg=new QRegExp("^char \\*([a-z]+[a-z0-9]+)=.*;$");

if (reg->indexIn(line)>-1){

arrays.append(reg->cap(1));

}else{

reg=new QRegExp("^int ([a-z]+[a-z0-9]+)\\[(.+)\\];$");

if (reg->indexIn(line)>-1){

arrays.append(reg->cap(1));

ParseExpr(reg->cap(2));

}else{

reg=new QRegExp("^([a-z]+):$");

if (reg->indexIn(line)>-1){

labels.append(reg->cap(1));

}else{

reg=new QRegExp("^break;$");

if (reg->indexIn(line)>-1);

else{

reg=new QRegExp("^goto (.*);$");

if (reg->indexIn(line)>-1){

nlabels.append(reg->cap(1));

}

else{

reg=new QRegExp("^int ([a-z]+);$");

if (reg->indexIn(line)>-1){

vars.append(reg->cap(1));

}

else{

reg=new QRegExp("^system \\(pause\\);$");

if (reg->indexIn(line)>-1);

else {

reg=new QRegExp("^printf \\((.*)\\);$");

if (reg->indexIn(line)>-1){

QStringList params=reg->cap(1).split(",");

foreach (QString param,params)

ParseExpr(param);

}else

{

reg=new QRegExp("^(.*)\\[(.*)\\]=(.*);$");

if (reg->indexIn(line)>-1){

QString array=reg->cap(1);

if (arrays.indexOf(array)>-1)

{

ParseExpr (reg->cap(2));

ParseExpr (reg->cap(3));

}else

{

result=7;

}

}else{

reg=new QRegExp("^(.*)=(.*);$");

if (reg->indexIn(line)>-1){

QString var=reg->cap(1);

if (vars.indexOf(var)>-1)

ParseExpr (reg->cap(2));

else

result=6;

}else

{

result=9;

}

}

}

}

}

}

}

}

}

}

}

}

}

}

}(result!=0)return line_nmb;

}(QString label,nlabels)

if (labels.indexOf(label)==-1)result=5;

}MainWindow::ParseExpr(QString expr){=expr.trimmed();(expr=="")result=4;ok;.toInt(&ok, 10);(ok)return;(vars.indexOf(expr)!=-1)return;*reg;=new QRegExp("^int ([a-z]+)$");(reg->indexIn(expr)>-1){

vars.append(reg->cap(1));

return;

}=new QRegExp("^int ([a-z]+)=([0-9]+)$");(reg->indexIn(expr)>-1){

vars.append(reg->cap(1));

return;

}=new QRegExp("^([a-z]+[a-z0-9]+)\\[(.+)\\]$");(reg->indexIn(expr)>-1){

if (arrays.indexOf(reg->cap(1))!=-1){

ParseExpr(reg->cap(2));

return;

}

}operands=QStringList()<<"||"<<"&&"<<"=="<<"!="<<"<<"<<">>"<<"<"<<"="<<"+"<<"-";(QString operand,operands)(expr.indexOf(operand)!=-1){

QStringList exprs=expr.split(operand);

foreach(QString exp,exprs)

ParseExpr(exp);

return;

}=new QRegExp("^'.'$");(reg->indexIn(expr)>-1)

return;=new QRegExp("^[a-z ]*$");(reg->indexIn(expr)>-1)

return;(expr=="'\\0'")return;=10;

}MainWindow::ShowCode(QString qwer){code=qwer.split("\n");incl=0;>clear();(QString line,code){

incl-=line.count("}");

line=line.trimmed();

for(int i=0;i<incl;i++)

line="\t"+line;

formated->append(line);

incl+=line.count("{");

}

}::~MainWindow()

{

}


Содержание Введение .Разработка технического задания на проектирование 1.1Общая постановка задачи 1.2Определение требований к программе .3Пре

Больше работ по теме:

КОНТАКТНЫЙ EMAIL: [email protected]

Скачать реферат © 2017 | Пользовательское соглашение

Скачать      Реферат

ПРОФЕССИОНАЛЬНАЯ ПОМОЩЬ СТУДЕНТАМ