Compare commits
15 Commits
7520f5ec2f
...
lab5
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a95dc6c9d | |||
| 78dddbc047 | |||
| e0211cb6c5 | |||
| d61b1b7a2d | |||
| c001ccb12a | |||
| fdd30a8854 | |||
| 35cf9b5ed4 | |||
| e3918eb442 | |||
| 207b428ad9 | |||
| 09da4c5c62 | |||
| cc5fbb2163 | |||
| 037a4cd8a0 | |||
| b64f7a3205 | |||
| b835502ed4 | |||
| 796ac33829 |
1
lab4/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
*.zip
|
||||
*.cmil
|
||||
*.o
|
||||
cmilan/src/cmilan.exe
|
||||
!cmilan/doc/cmilan.pdf
|
||||
|
||||
@@ -156,6 +156,7 @@ void Parser::factor()
|
||||
/*
|
||||
Множитель описывается следующими правилами:
|
||||
<factor> -> number | identifier | -<factor> | (<expression>) | READ
|
||||
| ++ identifier | -- identifier | identifier++ | identifier--
|
||||
*/
|
||||
if(see(T_NUMBER)) {
|
||||
int value = scanner_->getIntValue();
|
||||
@@ -168,7 +169,29 @@ void Parser::factor()
|
||||
next();
|
||||
codegen_->emit(LOAD, varAddress);
|
||||
//Если встретили переменную, то выгружаем значение, лежащее по ее адресу, на вершину стека
|
||||
|
||||
// Постфиксный инкремент или декремент
|
||||
if(see(T_INC) || see(T_DEC)) {
|
||||
codegen_->emit(DUP);
|
||||
codegen_->emit(PUSH, 1);
|
||||
codegen_->emit(see(T_INC) ? ADD : SUB);
|
||||
codegen_->emit(STORE, varAddress);
|
||||
next();
|
||||
}
|
||||
}
|
||||
// Префиксный инкремент или декремент
|
||||
else if(see(T_INC) || see(T_DEC)) {
|
||||
bool isIncrement = see(T_INC);
|
||||
next();
|
||||
mustBe(T_IDENTIFIER);
|
||||
int varAddress = findOrAddVariable(scanner_->getStringValue());
|
||||
|
||||
codegen_->emit(LOAD, varAddress);
|
||||
codegen_->emit(PUSH, 1);
|
||||
codegen_->emit(isIncrement ? ADD : SUB);
|
||||
codegen_->emit(DUP);
|
||||
codegen_->emit(STORE, varAddress);
|
||||
}
|
||||
else if(see(T_ADDOP) && scanner_->getArithmeticValue() == A_MINUS) {
|
||||
next();
|
||||
factor();
|
||||
|
||||
@@ -28,6 +28,8 @@ static const char * tokenNames_[] = {
|
||||
"'('",
|
||||
"')'",
|
||||
"';'",
|
||||
"'++'",
|
||||
"'--'",
|
||||
};
|
||||
|
||||
void Scanner::nextToken()
|
||||
@@ -185,15 +187,31 @@ void Scanner::nextToken()
|
||||
//Знаки операций. Для "+"/"-" получим лексему операции типа сложнения, и соответствующую операцию.
|
||||
//для "*" - лексему операции типа умножения
|
||||
case '+':
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_PLUS;
|
||||
nextChar();
|
||||
|
||||
// Ищем оператор инкремента
|
||||
if(ch_ == '+') {
|
||||
token_ = T_INC;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_PLUS;
|
||||
}
|
||||
break;
|
||||
|
||||
case '-':
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_MINUS;
|
||||
nextChar();
|
||||
|
||||
// Ищем оператор декремента
|
||||
if(ch_ == '-') {
|
||||
token_ = T_DEC;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_MINUS;
|
||||
}
|
||||
break;
|
||||
|
||||
case '*':
|
||||
|
||||
@@ -29,7 +29,9 @@ enum Token {
|
||||
T_CMP, // Сводная лексема для операторов отношения
|
||||
T_LPAREN, // Открывающая скобка
|
||||
T_RPAREN, // Закрывающая скобка
|
||||
T_SEMICOLON // ";"
|
||||
T_SEMICOLON, // ";"
|
||||
T_INC, // Оператор инкремента
|
||||
T_DEC // Оператор декремента
|
||||
};
|
||||
|
||||
// Функция tokenToString возвращает описание лексемы.
|
||||
|
||||
7
lab4/cmilan/test/incdec/inc.mil
Normal file
@@ -0,0 +1,7 @@
|
||||
BEGIN
|
||||
x := 0;
|
||||
write(x++);
|
||||
write(x);
|
||||
write(++x);
|
||||
write(x)
|
||||
END
|
||||
6
lab4/cmilan/test/incdec/incif.mil
Normal file
@@ -0,0 +1,6 @@
|
||||
BEGIN
|
||||
i := 1;
|
||||
j := 2;
|
||||
|
||||
IF i < --j THEN WRITE(100) ELSE WRITE(-100) FI
|
||||
END
|
||||
13
lab4/cmilan/test/incdec/incxy.mil
Normal file
@@ -0,0 +1,13 @@
|
||||
BEGIN
|
||||
y := x++;
|
||||
write(y);
|
||||
write(x);
|
||||
|
||||
y := 10 - --x;
|
||||
write(y);
|
||||
write(x);
|
||||
|
||||
y := 10 -++x;
|
||||
write(y);
|
||||
write(x)
|
||||
END
|
||||
5
lab4/cmilan/test/incdec/invalid.mil
Normal file
@@ -0,0 +1,5 @@
|
||||
BEGIN
|
||||
x := 10;
|
||||
y := 10 - --x; /* Корректно: минус и декремент разделены пробелом */
|
||||
y := 10 ---x; /* Некорректно: три минуса подряд */
|
||||
END
|
||||
3
lab4/cmilan/test/incdec/invalid2.mil
Normal file
@@ -0,0 +1,3 @@
|
||||
BEGIN
|
||||
x := --x++
|
||||
END
|
||||
5
lab4/report/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
BIN
lab4/report/img/compiler.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
lab4/report/img/result1.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
lab4/report/img/result2.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
lab4/report/img/result3.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
lab4/report/img/result4.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
lab4/report/img/result5.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
lab4/report/img/scheme.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
lab4/report/img/syntax_diagram_addop.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
lab4/report/img/syntax_diagram_cmpi.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
lab4/report/img/syntax_diagram_digit.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
lab4/report/img/syntax_diagram_expression.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
lab4/report/img/syntax_diagram_factor.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
lab4/report/img/syntax_diagram_ident.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
lab4/report/img/syntax_diagram_letter.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
lab4/report/img/syntax_diagram_mulop.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
lab4/report/img/syntax_diagram_number.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
lab4/report/img/syntax_diagram_program.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
lab4/report/img/syntax_diagram_relation.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
lab4/report/img/syntax_diagram_statement.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
lab4/report/img/syntax_diagram_statementList.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
979
lab4/report/report.tex
Normal file
@@ -0,0 +1,979 @@
|
||||
\documentclass[a4paper, final]{article}
|
||||
%\usepackage{literat} % Нормальные шрифты
|
||||
\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта
|
||||
\usepackage{tabularx}
|
||||
\usepackage[T2A]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[russian]{babel}
|
||||
\usepackage{amsmath}
|
||||
\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry}
|
||||
\usepackage{ragged2e} %для растягивания по ширине
|
||||
\usepackage{setspace} %для межстрочно го интервала
|
||||
\usepackage{moreverb} %для работы с листингами
|
||||
\usepackage{indentfirst} % для абзацного отступа
|
||||
\usepackage{moreverb} %для печати в листинге исходного кода программ
|
||||
\usepackage{pdfpages} %для вставки других pdf файлов
|
||||
\usepackage{tikz}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{afterpage}
|
||||
\usepackage{longtable}
|
||||
\usepackage{float}
|
||||
\usepackage{fancyvrb}
|
||||
|
||||
|
||||
% \usepackage[paper=A4,DIV=12]{typearea}
|
||||
\usepackage{pdflscape}
|
||||
% \usepackage{lscape}
|
||||
|
||||
\usepackage{array}
|
||||
\usepackage{multirow}
|
||||
|
||||
\renewcommand\verbatimtabsize{4\relax}
|
||||
\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге
|
||||
\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице
|
||||
\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы
|
||||
\usepackage{listings} %листинги
|
||||
\usepackage{xcolor} % цвета
|
||||
\usepackage{hyperref}% для гиперссылок
|
||||
\usepackage{enumitem} %для перечислений
|
||||
|
||||
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
|
||||
|
||||
|
||||
\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях
|
||||
|
||||
\hypersetup{colorlinks,
|
||||
allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные)
|
||||
|
||||
% подгружаемые языки — подробнее в документации listings (это всё для листингов)
|
||||
\lstloadlanguages{ SQL}
|
||||
% включаем кириллицу и добавляем кое−какие опции
|
||||
\lstset{tabsize=2,
|
||||
breaklines,
|
||||
basicstyle=\footnotesize,
|
||||
columns=fullflexible,
|
||||
flexiblecolumns,
|
||||
numbers=left,
|
||||
numberstyle={\footnotesize},
|
||||
keywordstyle=\color{blue},
|
||||
inputencoding=cp1251,
|
||||
extendedchars=true
|
||||
}
|
||||
\lstdefinelanguage{MyC}{
|
||||
language=SQL,
|
||||
% ndkeywordstyle=\color{darkgray}\bfseries,
|
||||
% identifierstyle=\color{black},
|
||||
% morecomment=[n]{/**}{*/},
|
||||
% commentstyle=\color{blue}\ttfamily,
|
||||
% stringstyle=\color{red}\ttfamily,
|
||||
% morestring=[b]",
|
||||
% showstringspaces=false,
|
||||
% morecomment=[l][\color{gray}]{//},
|
||||
keepspaces=true,
|
||||
escapechar=\%,
|
||||
texcl=true
|
||||
}
|
||||
|
||||
\textheight=24cm % высота текста
|
||||
\textwidth=16cm % ширина текста
|
||||
\oddsidemargin=0pt % отступ от левого края
|
||||
\topmargin=-1.5cm % отступ от верхнего края
|
||||
\parindent=24pt % абзацный отступ
|
||||
\parskip=5pt % интервал между абзацами
|
||||
\tolerance=2000 % терпимость к "жидким" строкам
|
||||
\flushbottom % выравнивание высоты страниц
|
||||
|
||||
|
||||
% Настройка листингов
|
||||
\lstset{
|
||||
language=python,
|
||||
extendedchars=\true,
|
||||
inputencoding=utf8,
|
||||
keepspaces=true,
|
||||
% captionpos=b, % подписи листингов снизу
|
||||
}
|
||||
|
||||
\begin{document} % начало документа
|
||||
|
||||
|
||||
|
||||
% НАЧАЛО ТИТУЛЬНОГО ЛИСТА
|
||||
\begin{center}
|
||||
\hfill \break
|
||||
\hfill \break
|
||||
\normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\
|
||||
федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]}
|
||||
\normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt]
|
||||
\normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt]
|
||||
\normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\
|
||||
|
||||
\hfill \break
|
||||
\hfill \break
|
||||
\hfill \break
|
||||
\hfill \break
|
||||
\large{Лабораторная работа №4}\\
|
||||
\large{<<Доработка компилятора языка MiLan>>}\\
|
||||
\large{по дисциплине}\\
|
||||
\large{<<Математическая логика и теория автоматов>>}\\
|
||||
\large{Вариант 8}\\
|
||||
|
||||
% \hfill \break
|
||||
\hfill \break
|
||||
\end{center}
|
||||
|
||||
\small{
|
||||
\begin{tabular}{lrrl}
|
||||
\!\!\!Студент, & \hspace{2cm} & & \\
|
||||
\!\!\!группы 5130201/20102 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\
|
||||
\!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Востров А. В. \\\\
|
||||
&&\hspace{4cm}
|
||||
\end{tabular}
|
||||
\begin{flushright}
|
||||
<<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025г.
|
||||
\end{flushright}
|
||||
}
|
||||
|
||||
\hfill \break
|
||||
% \hfill \break
|
||||
\begin{center} \small{Санкт-Петербург, 2025} \end{center}
|
||||
\thispagestyle{empty} % выключаем отображение номера для этой страницы
|
||||
|
||||
% КОНЕЦ ТИТУЛЬНОГО ЛИСТА
|
||||
\newpage
|
||||
|
||||
\tableofcontents
|
||||
|
||||
|
||||
\newpage
|
||||
|
||||
\section*{Введение}
|
||||
\addcontentsline{toc}{section}{Введение}
|
||||
Лабораторная №4 заключается в доработке компилятора языка MiLan согласно варианту работы.
|
||||
|
||||
\textit{Вариант 8}: В компилятор необходимо добавить поддержку операций инкремента и декремента, как постфиксных, так и префиксных: \texttt{++i}, \texttt{\textminus{}\textminus{}i}, \texttt{i++}, \texttt{i\textminus{}\textminus{}}. С точки зрения действия на операнд i между префиксными и постфиксными операциями разницы нет. Если эти выражения являются частью более сложного выражения, то при префиксной форме сначала изменяется операнд i, и уже измененный результат используется в выражении. При постфиксной форме операторов инкремента или декремента сначала операнд i используется в выражении, а потом уже изменяется.
|
||||
|
||||
|
||||
\newpage
|
||||
\section {Математическое описание}
|
||||
\subsection{Обзор языка MiLan}
|
||||
Язык Милан — учебный язык программирования, описанный в учебнике~\cite{karpov}.
|
||||
|
||||
Программа на Милане представляет собой последовательность операторов, заключенных между ключевыми словами \texttt{begin} и \texttt{end}. Операторы отделяются друг от друга точкой с запятой. После последнего оператора в блоке точка с запятой не ставится. Компилятор \texttt{CMilan} не учитывает регистр символов в именах переменных и ключевых словах.
|
||||
|
||||
В базовую версию языка Милан входят следующие конструкции: константы, идентификаторы, арифметические операции над целыми числами, операторы чтения чисел со стандартного ввода и печати чисел на стандартный вывод, оператор присваивания, условный оператор, оператор цикла с предусловием.
|
||||
|
||||
Программа может содержать комментарии, которые могут быть многострочными. Комментарий начинается символами \texttt{/*} и заканчивается символами \texttt{*/}. Вложенные комментарии не допускаются.
|
||||
|
||||
В данной лабораторной рассматривается добавление поддержки операций инкремента и декремента, как постфиксных, так и префиксных: \texttt{++i}, \texttt{\textminus{}\textminus{}i}, \texttt{i++}, \texttt{i\textminus{}\textminus{}}. С точки зрения действия на операнд i между префиксными и постфиксными операциями разницы нет. Если эти выражения являются частью более сложного выражения, то при префиксной форме сначала изменяется операнд i, и уже измененный результат используется в выражении. При постфиксной форме операторов инкремента или декремента сначала операнд i используется в выражении, а потом уже изменяется.
|
||||
|
||||
\subsection{Лексический анализ}
|
||||
В реальных трансляторах языков программирования (ЯП) первой фазой является так называемый лексический анализ входной программы — предварительная
|
||||
обработка входного текста с выделением в нем структурно значимых единиц — лексем. На Рис.~\ref{fig:scheme} представлена схема транслятора.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{img/scheme.png}
|
||||
\caption{Схема транслятора.}
|
||||
\label{fig:scheme}
|
||||
\end{figure}
|
||||
|
||||
Лексемы — минимальные единицы языка, которые имеют смысл.
|
||||
|
||||
Значение лексемы, определяющее подстроку символов входной цепочки, соответствующих распознанному классу лексемы. В зависимости от класса, значение
|
||||
лексемы может быть преобразовано во внутреннее представление уже на этапе лексического анализа.
|
||||
|
||||
Класс лексемы, определяющий общее название для категории элементов, обладающих общими свойствами (идентификатор, целое число, строка символов...).
|
||||
|
||||
Лексический анализатор обрабатывает входную цепочку, а на его вход подаются
|
||||
символы, сгруппированные по категориям. Поэтому перед лексическим анализом
|
||||
осуществляется дополнительная обработка, сопоставляющая с каждым символом его
|
||||
класс, что позволяет сканеру манипулировать единым понятием для целой группы
|
||||
символов.
|
||||
|
||||
Лексический анализатор (лексер) — это конечный автомат, который преобразует входную строку символов в последовательность токенов. Формально его можно описать следующим образом:
|
||||
|
||||
Пусть заданы:
|
||||
\begin{itemize}
|
||||
\item $\Sigma$ — входной алфавит (множество допустимых символов)
|
||||
\item $T$ — множество типов токенов
|
||||
\item $D$ — множество допустимых значений токенов
|
||||
\end{itemize}
|
||||
|
||||
Тогда лексический анализатор реализует отображение:
|
||||
\[
|
||||
F_{\text{lexer}} : \Sigma^* \rightarrow (T \times D)^*
|
||||
\]
|
||||
|
||||
где:
|
||||
\begin{itemize}
|
||||
\item $\Sigma^*$ — множество всех возможных строк над алфавитом $\Sigma$
|
||||
\item $(T \times D)^*$ — множество последовательностей пар (тип токена, значение)
|
||||
\end{itemize}
|
||||
|
||||
Процесс лексического анализа можно представить как \textbf{детерминированный конечный автомат (ДКА)}:
|
||||
\[
|
||||
M = (Q, \Sigma, \delta, q_0, F),
|
||||
\]
|
||||
где:
|
||||
\begin{itemize}
|
||||
\item $Q$ — множество состояний автомата
|
||||
\item $\delta : Q \times \Sigma \rightarrow Q$ — функция переходов
|
||||
\item $q_0 \in Q$ — начальное состояние
|
||||
\item $F \subseteq Q$ — множество конечных состояний
|
||||
\end{itemize}
|
||||
|
||||
Для каждого распознанного токена $t_i$ выполняется:
|
||||
\[
|
||||
t_i = (\text{type}, \text{value}), \quad \text{где } \text{type} \in T, \text{value} \in D
|
||||
\]
|
||||
|
||||
\subsection{Синтаксический анализ}
|
||||
Синтаксический анализ — процесс сопоставления линейной последовательности лексем естественного или формального языка с его формальной грамматикой.
|
||||
Результатом обычно является дерево разбора. Обычно применяется совместно с лексическим анализом.
|
||||
|
||||
Синтаксический анализатор выражений (парсер) — часть программы,
|
||||
выполняющая чтение и анализ выражения.
|
||||
|
||||
Существует два типа алгоритмов синтаксического анализа: нисходящий и восходящий:
|
||||
\begin{itemize}
|
||||
\item Нисходящий парсер — продукции грамматики раскрываются, начиная со стартового символа, до получения требуемой последовательности токенов.
|
||||
\item Восходящий парсер — продукции восстанавливаются из правых частей, начиная с токенов и кончая стартовым символом.
|
||||
\end{itemize}
|
||||
|
||||
Грамматика языка MiLan использует нисходящий парсер. При восстановлении
|
||||
синтаксического дерева при нисходящем разборе слева направо последовательно анализирует все поддеревья, принадлежащие самому левому нетерминалу. Когда самым
|
||||
левым становится другой нетерминал, анализируется уже он.
|
||||
|
||||
Компилятор CMilan включает три компонента (Рис.~\ref{fig:compiler}):
|
||||
\begin{enumerate}
|
||||
\item лексический анализатор;
|
||||
\item синтаксический анализатор;
|
||||
\item генератор команд виртуальной машины Милана.
|
||||
\end{enumerate}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{img/compiler.png}
|
||||
\caption{Компоненты компилятора CMilan.}
|
||||
\label{fig:compiler}
|
||||
\end{figure}
|
||||
|
||||
\subsection{Грамматика языка MiLan}
|
||||
Грамматика языка Милан является контекстно-свободной, так как удовлетворяет определению КС-грамматики, т.е. продукции грамматики имеют вид $A \rightarrow \beta$, где $A$ – одиночный нетерминал, а $\beta$ – произвольная цепочка из терминалов и нетерминалов. Более того, грамматика языка Милан является LL(1) грамматикой, так как необходимо просмотреть поток всего на один символ вперед при принятии решения о том, какое правило грамматики необходимо применить. В данной работе в грамматику были добавлены операции инкремента и декремента, однако это не повлияло на LL(1) свойство грамматики.
|
||||
|
||||
Грамматика языка Милан, расширенная операцией инкремента и декремента, в форме Бэкуса-Наура приведена ниже:
|
||||
|
||||
\begin{Verbatim}[commandchars=\\\{\}]
|
||||
<program> ::= ‘begin’ <statementList> ‘end’
|
||||
<statementList> ::= <statement> ‘;’ <statementList>
|
||||
| epsilon
|
||||
<statement> ::= <ident> ‘:=’ <expression>
|
||||
| ‘if’ <relation> ‘then’
|
||||
<statementList> [‘else’ <statementList>] ‘fi’
|
||||
| ‘while’ <relation> ‘do’ <statementList> ‘od’
|
||||
| ‘write’ ‘(’ <expression> ‘)’
|
||||
<expression> ::= <term> {<addop> <term>}
|
||||
<term> ::= <factor> {<mulop> <factor>}
|
||||
<factor> ::= <ident>
|
||||
| <number>
|
||||
| ‘(’ <expression> ‘)’
|
||||
| ‘read’
|
||||
| ‘-’ <factor>
|
||||
\textcolor{green!60!black}{| <ident> ‘++’}
|
||||
\textcolor{green!60!black}{| <ident> ‘--’}
|
||||
\textcolor{green!60!black}{| ‘++’ <ident>}
|
||||
\textcolor{green!60!black}{| ‘--’ <ident>}
|
||||
<relation> ::= <expression> <cmpi> <expression>
|
||||
<addop> ::= ‘+’|‘-’
|
||||
<mulop> ::= ‘*’|‘/’
|
||||
<cmpi> ::= ‘=’|‘!=’|‘<’|‘<=’|‘>’|‘>=’
|
||||
<number> ::= <digit> {<digit>}
|
||||
<ident> ::= <letter> {<letter> | <digit>}
|
||||
<letter> ::= ‘a’|‘b’|‘c’ | ...| ‘z’|‘A’|‘B’|‘C’ | ...| ‘Z’
|
||||
<digit> ::= ‘0’|‘1’|‘2’|‘3’|‘4’|‘5’|‘6’|‘7’|‘8’|‘9’
|
||||
\end{Verbatim}
|
||||
|
||||
Изменения коснулись только правила \texttt{<factor>}, в котором были добавлены 4 новые продукции, описывающие операции постфиксного и префиксного инкремента и декремента.
|
||||
|
||||
Синтаксические диаграммы для всех нетерминалов грамматики приведены на Рис.~\ref{fig:syntax_diagram_program} — Рис.~\ref{fig:syntax_diagram_digit}. Обновлённая синтаксическая диаграмма для нетерминала \texttt{<factor>} приведена на Рис.~\ref{fig:syntax_diagram_factor}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.9\linewidth]{img/syntax_diagram_program.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<program>}.}
|
||||
\label{fig:syntax_diagram_program}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{img/syntax_diagram_statementList.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<statementList>}.}
|
||||
\label{fig:syntax_diagram_statementList}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/syntax_diagram_statement.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<statement>}.}
|
||||
\label{fig:syntax_diagram_statement}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{img/syntax_diagram_expression.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<expression>}.}
|
||||
\label{fig:syntax_diagram_expression}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/syntax_diagram_relation.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<relation>}.}
|
||||
\label{fig:syntax_diagram_relation}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{img/syntax_diagram_addop.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<addop>}.}
|
||||
\label{fig:syntax_diagram_addop}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{img/syntax_diagram_mulop.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<mulop>}.}
|
||||
\label{fig:syntax_diagram_mulop}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{img/syntax_diagram_factor.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<factor>}, дополненная операциями инкремента и декремента (отмечены зеленым цветом).}
|
||||
\label{fig:syntax_diagram_factor}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.6\linewidth]{img/syntax_diagram_number.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<number>}.}
|
||||
\label{fig:syntax_diagram_number}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.6\linewidth]{img/syntax_diagram_ident.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<ident>}.}
|
||||
\label{fig:syntax_diagram_ident}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/syntax_diagram_letter.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<letter>}.}
|
||||
\label{fig:syntax_diagram_letter}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=1\linewidth]{img/syntax_diagram_digit.png}
|
||||
\caption{Синтаксическая диаграмма для нетерминала \texttt{<digit>}.}
|
||||
\label{fig:syntax_diagram_digit}
|
||||
\end{figure}
|
||||
|
||||
\newpage
|
||||
\phantom{text}
|
||||
|
||||
\newpage
|
||||
\phantom{text}
|
||||
|
||||
\newpage
|
||||
\phantom{text}
|
||||
|
||||
\section{Особенности реализации}
|
||||
\subsection{Изменения в лексическом анализаторе}
|
||||
|
||||
\subsubsection{Файл \texttt{Scanner.h}}
|
||||
В перечисление \texttt{Token} в файле \texttt{Scanner.h} были добавлены новые токены: \texttt{INC}, \texttt{DEC}. Код обновлённого перечисления представлен в листинге~\ref{lst:token}, добавлены строки 24-25.
|
||||
|
||||
\begin{lstlisting}[language=C++, caption=Обновлённое перечисление \texttt{Token}, label=lst:token]
|
||||
enum Token {
|
||||
T_EOF, // Конец текстового потока
|
||||
T_ILLEGAL, // Признак недопустимого символа
|
||||
T_IDENTIFIER, // Идентификатор
|
||||
T_NUMBER, // Целочисленный литерал
|
||||
T_BEGIN, // Ключевое слово "begin"
|
||||
T_END, // Ключевое слово "end"
|
||||
T_IF, // Ключевое слово "if"
|
||||
T_THEN, // Ключевое слово "then"
|
||||
T_ELSE, // Ключевое слово "else"
|
||||
T_FI, // Ключевое слово "fi"
|
||||
T_WHILE, // Ключевое слово "while"
|
||||
T_DO, // Ключевое слово "do"
|
||||
T_OD, // Ключевое слово "od"
|
||||
T_WRITE, // Ключевое слово "write"
|
||||
T_READ, // Ключевое слово "read"
|
||||
T_ASSIGN, // Оператор ":="
|
||||
T_ADDOP, // Сводная лексема для "+" и "-" (операция типа сложения)
|
||||
T_MULOP, // Сводная лексема для "*" и "/" (операция типа умножения)
|
||||
T_CMP, // Сводная лексема для операторов отношения
|
||||
T_LPAREN, // Открывающая скобка
|
||||
T_RPAREN, // Закрывающая скобка
|
||||
T_SEMICOLON, // ";"
|
||||
T_INC, // Оператор инкремента
|
||||
T_DEC // Оператор декремента
|
||||
};
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Файл \texttt{Scanner.cpp}}
|
||||
В массив названий токенов \texttt{tokenNames\_} были добавлены новые строки, соответствующие инкременту и декременту: \texttt{"++"} и \texttt{"\textminus\textminus"}, соответственно. Код обновлённого массива представлен в листинге~\ref{lst:token_names}, добавлены строки 24-25.
|
||||
|
||||
\begin{lstlisting}[language=C++, caption=Обновлённый массив \texttt{tokenNames\_}, label=lst:token_names]
|
||||
static const char * tokenNames_[] = {
|
||||
"end of file",
|
||||
"illegal token",
|
||||
"identifier",
|
||||
"number",
|
||||
"'BEGIN'",
|
||||
"'END'",
|
||||
"'IF'",
|
||||
"'THEN'",
|
||||
"'ELSE'",
|
||||
"'FI'",
|
||||
"'WHILE'",
|
||||
"'DO'",
|
||||
"'OD'",
|
||||
"'WRITE'",
|
||||
"'READ'",
|
||||
"':='",
|
||||
"'+' or '-'",
|
||||
"'*' or '/'",
|
||||
"comparison operator",
|
||||
"'('",
|
||||
"')'",
|
||||
"';'",
|
||||
"'++'",
|
||||
"'--'",
|
||||
};
|
||||
\end{lstlisting}
|
||||
|
||||
Также была обновлена функция \texttt{nextToken()}. В ней были добавлены новые условия для распознавания инкремента и декремента. Код обновлённой функции представлен в листинге~\ref{lst:next_token}, изменения коснулись строк 158-181.
|
||||
|
||||
\begin{lstlisting}[language=C++, caption=Обновлённая функция \texttt{nextToken()}, label=lst:next_token]
|
||||
void Scanner::nextToken()
|
||||
{
|
||||
skipSpace();
|
||||
|
||||
// Пропускаем комментарии
|
||||
// Если встречаем "/", то за ним должна идти "*". Если "*" не встречена, считаем, что встретили операцию деления
|
||||
// и лексему - операция типа умножения. Дальше смотрим все символы, пока не находим звездочку или символ конца файла.
|
||||
// Если нашли * - проверяем на наличие "/" после нее. Если "/" не найден - ищем следующую "*".
|
||||
while(ch_ == '/') {
|
||||
nextChar();
|
||||
if(ch_ == '*') {
|
||||
nextChar();
|
||||
bool inside = true;
|
||||
while(inside) {
|
||||
while(ch_ != '*' && !input_.eof()) {
|
||||
nextChar();
|
||||
}
|
||||
|
||||
if(input_.eof()) {
|
||||
token_ = T_EOF;
|
||||
return;
|
||||
}
|
||||
|
||||
nextChar();
|
||||
if(ch_ == '/') {
|
||||
inside = false;
|
||||
nextChar();
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
token_ = T_MULOP;
|
||||
arithmeticValue_ = A_DIVIDE;
|
||||
return;
|
||||
}
|
||||
|
||||
skipSpace();
|
||||
}
|
||||
|
||||
//Если встречен конец файла, считаем за лексему конца файла.
|
||||
if(input_.eof()) {
|
||||
token_ = T_EOF;
|
||||
return;
|
||||
}
|
||||
//Если встретили цифру, то до тех пока дальше идут цифры - считаем как продолжение числа.
|
||||
//Запоминаем полученное целое, а за лексему считаем целочисленный литерал
|
||||
|
||||
if(isdigit(ch_)) {
|
||||
int value = 0;
|
||||
while(isdigit(ch_)) {
|
||||
value = value * 10 + (ch_ - '0'); //поразрядное считывание, преобразуем символьное значение к числу.
|
||||
nextChar();
|
||||
}
|
||||
token_ = T_NUMBER;
|
||||
intValue_ = value;
|
||||
}
|
||||
//Если же следующий символ - буква ЛА - тогда считываем до тех пор, пока дальше буквы ЛА или цифры.
|
||||
//Как только считали имя переменной, сравниваем ее со списком зарезервированных слов. Если не совпадает ни с одним из них,
|
||||
//считаем, что получили переменную, имя которой запоминаем, а за текущую лексему считаем лексему идентификатора.
|
||||
//Если совпадает с каким-либо словом из списка - считаем что получили лексему, соответствующую этому слову.
|
||||
else if(isIdentifierStart(ch_)) {
|
||||
string buffer;
|
||||
while(isIdentifierBody(ch_)) {
|
||||
buffer += ch_;
|
||||
nextChar();
|
||||
}
|
||||
|
||||
transform(buffer.begin(), buffer.end(), buffer.begin(), ::tolower);
|
||||
|
||||
map<string, Token>::iterator kwd = keywords_.find(buffer);
|
||||
if(kwd == keywords_.end()) {
|
||||
token_ = T_IDENTIFIER;
|
||||
stringValue_ = buffer;
|
||||
}
|
||||
else {
|
||||
token_ = kwd->second;
|
||||
}
|
||||
}
|
||||
//Символ не является буквой, цифрой, "/" или признаком конца файла
|
||||
else {
|
||||
switch(ch_) {
|
||||
//Признак лексемы открывающей скобки - встретили "("
|
||||
case '(':
|
||||
token_ = T_LPAREN;
|
||||
nextChar();
|
||||
break;
|
||||
//Признак лексемы закрывающей скобки - встретили ")"
|
||||
case ')':
|
||||
token_ = T_RPAREN;
|
||||
nextChar();
|
||||
break;
|
||||
//Признак лексемы ";" - встретили ";"
|
||||
case ';':
|
||||
token_ = T_SEMICOLON;
|
||||
nextChar();
|
||||
break;
|
||||
//Если встречаем ":", то дальше смотрим наличие символа "=". Если находим, то считаем что нашли лексему присваивания
|
||||
//Иначе - лексема ошибки.
|
||||
case ':':
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
token_ = T_ASSIGN;
|
||||
nextChar();
|
||||
|
||||
}
|
||||
else {
|
||||
token_ = T_ILLEGAL;
|
||||
}
|
||||
break;
|
||||
//Если встретили символ "<", то либо следующий символ "=", тогда лексема нестрогого сравнения. Иначе - строгого.
|
||||
case '<':
|
||||
token_ = T_CMP;
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
cmpValue_ = C_LE;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
cmpValue_ = C_LT;
|
||||
}
|
||||
break;
|
||||
//Аналогично предыдущему случаю
|
||||
case '>':
|
||||
token_ = T_CMP;
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
cmpValue_ = C_GE;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
cmpValue_ = C_GT;
|
||||
}
|
||||
break;
|
||||
//Если встретим "!", то дальше должно быть "=", тогда считаем, что получили лексему сравнения
|
||||
//и знак "!=" иначе считаем, что у нас лексема ошибки
|
||||
case '!':
|
||||
nextChar();
|
||||
if(ch_ == '=') {
|
||||
nextChar();
|
||||
token_ = T_CMP;
|
||||
cmpValue_ = C_NE;
|
||||
}
|
||||
else {
|
||||
token_ = T_ILLEGAL;
|
||||
}
|
||||
break;
|
||||
//Если встретим "=" - лексема сравнения и знак "="
|
||||
case '=':
|
||||
token_ = T_CMP;
|
||||
cmpValue_ = C_EQ;
|
||||
nextChar();
|
||||
break;
|
||||
//Знаки операций. Для "+"/"-" получим лексему операции типа сложнения, и соответствующую операцию.
|
||||
//для "*" - лексему операции типа умножения
|
||||
case '+':
|
||||
nextChar();
|
||||
|
||||
// Ищем оператор инкремента
|
||||
if(ch_ == '+') {
|
||||
token_ = T_INC;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_PLUS;
|
||||
}
|
||||
break;
|
||||
|
||||
case '-':
|
||||
nextChar();
|
||||
|
||||
// Ищем оператор декремента
|
||||
if(ch_ == '-') {
|
||||
token_ = T_DEC;
|
||||
nextChar();
|
||||
}
|
||||
else {
|
||||
token_ = T_ADDOP;
|
||||
arithmeticValue_ = A_MINUS;
|
||||
}
|
||||
break;
|
||||
|
||||
case '*':
|
||||
token_ = T_MULOP;
|
||||
arithmeticValue_ = A_MULTIPLY;
|
||||
nextChar();
|
||||
break;
|
||||
//Иначе лексема ошибки.
|
||||
default:
|
||||
token_ = T_ILLEGAL;
|
||||
nextChar();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection{Изменения в компиляторе}
|
||||
|
||||
\subsubsection{Файл \texttt{Parser.cpp}}
|
||||
В метод \texttt{factor()} объекта \texttt{Parser} была добавлена логика генерации команд для префиксного и постфиксного инкремента и декремента. Код обновлённого метода представлен в листинге~\ref{lst:parser_cpp}, изменения коснулись строк 20-27 (постфиксный инкремент и декремент) и строк 29-41 (префиксный инкремент и декремент).
|
||||
|
||||
Для постфиксного инкремента и декремента генерируется следующая последовательность команд виртуальной машины языка MiLan:
|
||||
\begin{itemize}
|
||||
\item \texttt{LOAD <адрес переменной>} - загрузка значения переменной на вершину стека.
|
||||
\item \texttt{DUP} - дублирование значения на вершине стека до выполнения операции инкремента или декремента, так как постфиксная операция возвращает старое значение переменной.
|
||||
\item \texttt{PUSH 1} - загрузка константы 1 на вершину стека
|
||||
\item \texttt{ADD} или \texttt{SUB} - сложение или вычитание значения на вершине стека с константой 1.
|
||||
\item \texttt{STORE <адрес переменной>} - сохранение результата на вершине стека по адресу переменной.
|
||||
\end{itemize}
|
||||
|
||||
Для префиксного инкремента и декремента генерируется следующая последовательность команд виртуальной машины языка MiLan:
|
||||
\begin{itemize}
|
||||
\item \texttt{LOAD <адрес переменной>} - загрузка значения переменной на вершину стека.
|
||||
\item \texttt{PUSH 1} - загрузка константы 1 на вершину стека
|
||||
\item \texttt{ADD} или \texttt{SUB} - сложение или вычитание значения на вершине стека с константой 1.
|
||||
\item \texttt{DUP} - дублирование значения на вершине стека после выполнения операции инкремента или декремента, так как префиксная операция возвращает новое значение переменной.
|
||||
\item \texttt{STORE <адрес переменной>} - сохранение результата на вершине стека по адресу переменной.
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\begin{lstlisting}[language=C++, caption=Обновлённый метод \texttt{factor()}, label=lst:parser_cpp]
|
||||
void Parser::factor()
|
||||
{
|
||||
/*
|
||||
Множитель описывается следующими правилами:
|
||||
<factor> -> number | identifier | -<factor> | (<expression>) | READ
|
||||
| ++ identifier | -- identifier | identifier++ | identifier--
|
||||
*/
|
||||
if(see(T_NUMBER)) {
|
||||
int value = scanner_->getIntValue();
|
||||
next();
|
||||
codegen_->emit(PUSH, value);
|
||||
//Если встретили число, то преобразуем его в целое и записываем на вершину стека
|
||||
}
|
||||
else if(see(T_IDENTIFIER)) {
|
||||
int varAddress = findOrAddVariable(scanner_->getStringValue());
|
||||
next();
|
||||
codegen_->emit(LOAD, varAddress);
|
||||
//Если встретили переменную, то выгружаем значение, лежащее по ее адресу, на вершину стека
|
||||
|
||||
// Постфиксный инкремент или декремент
|
||||
if(see(T_INC) || see(T_DEC)) {
|
||||
codegen_->emit(DUP);
|
||||
codegen_->emit(PUSH, 1);
|
||||
codegen_->emit(see(T_INC) ? ADD : SUB);
|
||||
codegen_->emit(STORE, varAddress);
|
||||
next();
|
||||
}
|
||||
}
|
||||
// Префиксный инкремент или декремент
|
||||
else if(see(T_INC) || see(T_DEC)) {
|
||||
bool isIncrement = see(T_INC);
|
||||
next();
|
||||
mustBe(T_IDENTIFIER);
|
||||
int varAddress = findOrAddVariable(scanner_->getStringValue());
|
||||
|
||||
codegen_->emit(LOAD, varAddress);
|
||||
codegen_->emit(PUSH, 1);
|
||||
codegen_->emit(isIncrement ? ADD : SUB);
|
||||
codegen_->emit(DUP);
|
||||
codegen_->emit(STORE, varAddress);
|
||||
}
|
||||
else if(see(T_ADDOP) && scanner_->getArithmeticValue() == A_MINUS) {
|
||||
next();
|
||||
factor();
|
||||
codegen_->emit(INVERT);
|
||||
//Если встретили знак "-", и за ним <factor> то инвертируем значение, лежащее на вершине стека
|
||||
}
|
||||
else if(match(T_LPAREN)) {
|
||||
expression();
|
||||
mustBe(T_RPAREN);
|
||||
//Если встретили открывающую скобку, тогда следом может идти любое арифметическое выражение и обязательно
|
||||
//закрывающая скобка.
|
||||
}
|
||||
else if(match(T_READ)) {
|
||||
codegen_->emit(INPUT);
|
||||
//Если встретили зарезервированное слово READ, то записываем на вершину стека идет запись со стандартного ввода
|
||||
}
|
||||
else {
|
||||
reportError("expression expected.");
|
||||
}
|
||||
}
|
||||
\end{lstlisting}
|
||||
|
||||
\newpage
|
||||
\section{Результаты работы программы}
|
||||
\subsection*{Программа №1}
|
||||
Исходный код программы представлен в листинге~\ref{lst:program1}.
|
||||
|
||||
\begin{lstlisting}[caption=Исходный код программы №1, label=lst:program1]
|
||||
BEGIN
|
||||
x := 0;
|
||||
write(x++);
|
||||
write(x);
|
||||
write(++x);
|
||||
write(x)
|
||||
END
|
||||
\end{lstlisting}
|
||||
|
||||
Последовательность команд, сгенерированная компилятором для данной программы, представлена в листинге~\ref{lst:program1_commands}.
|
||||
|
||||
|
||||
\begin{lstlisting}[caption={Последовательность команд, сгенерированная компилятором для программы №1.}, label={lst:program1_commands}, numbers=none]
|
||||
0: PUSH 0
|
||||
1: STORE 0
|
||||
2: LOAD 0
|
||||
3: DUP
|
||||
4: PUSH 1
|
||||
5: ADD
|
||||
6: STORE 0
|
||||
7: PRINT
|
||||
8: LOAD 0
|
||||
9: PRINT
|
||||
10: LOAD 0
|
||||
11: PUSH 1
|
||||
12: ADD
|
||||
13: DUP
|
||||
14: STORE 0
|
||||
15: PRINT
|
||||
16: LOAD 0
|
||||
17: PRINT
|
||||
18: STOP
|
||||
\end{lstlisting}
|
||||
|
||||
Результаты работы виртуальной машины для программы №1 представлены на Рис.~\ref{fig:result1}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.4\linewidth]{img/result1.png}
|
||||
\caption{Результаты работы виртуальной машины для программы №1.}
|
||||
\label{fig:result1}
|
||||
\end{figure}
|
||||
|
||||
\subsection*{Программа №2}
|
||||
Исходный код программы представлен в листинге~\ref{lst:program2}.
|
||||
|
||||
\begin{lstlisting}[caption=Исходный код программы №2, label=lst:program2]
|
||||
BEGIN
|
||||
i := 1;
|
||||
j := 2;
|
||||
|
||||
IF i < --j THEN WRITE(100) ELSE WRITE(-100) FI
|
||||
END
|
||||
\end{lstlisting}
|
||||
|
||||
Последовательность команд, сгенерированная компилятором для данной программы, представлена в листинге~\ref{lst:program2_commands}.
|
||||
|
||||
|
||||
\begin{lstlisting}[caption={Последовательность команд, сгенерированная компилятором для программы №2.}, label={lst:program2_commands}, numbers=none]
|
||||
0: PUSH 1
|
||||
1: STORE 0
|
||||
2: PUSH 2
|
||||
3: STORE 1
|
||||
4: LOAD 0
|
||||
5: LOAD 1
|
||||
6: PUSH 1
|
||||
7: SUB
|
||||
8: DUP
|
||||
9: STORE 1
|
||||
10: COMPARE 2
|
||||
11: JUMP_NO 15
|
||||
12: PUSH 100
|
||||
13: PRINT
|
||||
14: JUMP 18
|
||||
15: PUSH 100
|
||||
16: INVERT
|
||||
17: PRINT
|
||||
18: STOP
|
||||
\end{lstlisting}
|
||||
|
||||
Результаты работы виртуальной машины для программы №2 представлены на Рис.~\ref{fig:result2}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.4\linewidth]{img/result2.png}
|
||||
\caption{Результаты работы виртуальной машины для программы №2.}
|
||||
\label{fig:result2}
|
||||
\end{figure}
|
||||
|
||||
\subsection*{Программа №3}
|
||||
Исходный код программы представлен в листинге~\ref{lst:program3}.
|
||||
|
||||
\begin{lstlisting}[caption=Исходный код программы №3, label=lst:program3]
|
||||
BEGIN
|
||||
y := x++;
|
||||
write(y);
|
||||
write(x);
|
||||
|
||||
y := 10 - --x;
|
||||
write(y);
|
||||
write(x);
|
||||
|
||||
y := 10 -++x;
|
||||
write(y);
|
||||
write(x)
|
||||
END
|
||||
\end{lstlisting}
|
||||
|
||||
Последовательность команд, сгенерированная компилятором для данной программы, представлена в листинге~\ref{lst:program3_commands}.
|
||||
|
||||
\begin{lstlisting}[caption={Последовательность команд, сгенерированная компилятором для программы №3.}, label={lst:program3_commands}, numbers=none]
|
||||
0: LOAD 1
|
||||
1: DUP
|
||||
2: PUSH 1
|
||||
3: ADD
|
||||
4: STORE 1
|
||||
5: STORE 0
|
||||
6: LOAD 0
|
||||
7: PRINT
|
||||
8: LOAD 1
|
||||
9: PRINT
|
||||
10: PUSH 10
|
||||
11: LOAD 1
|
||||
12: PUSH 1
|
||||
13: SUB
|
||||
14: DUP
|
||||
15: STORE 1
|
||||
16: SUB
|
||||
17: STORE 0
|
||||
18: LOAD 0
|
||||
19: PRINT
|
||||
20: LOAD 1
|
||||
21: PRINT
|
||||
22: PUSH 10
|
||||
23: LOAD 1
|
||||
24: PUSH 1
|
||||
25: ADD
|
||||
26: DUP
|
||||
27: STORE 1
|
||||
28: SUB
|
||||
29: STORE 0
|
||||
30: LOAD 0
|
||||
31: PRINT
|
||||
32: LOAD 1
|
||||
33: PRINT
|
||||
34: STOP
|
||||
\end{lstlisting}
|
||||
|
||||
Результаты работы виртуальной машины для программы №3 представлены на Рис.~\ref{fig:result3}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.4\linewidth]{img/result3.png}
|
||||
\caption{Результаты работы виртуальной машины для программы №3.}
|
||||
\label{fig:result3}
|
||||
\end{figure}
|
||||
|
||||
\subsection*{Программа №4}
|
||||
Исходный код программы представлен в листинге~\ref{lst:program4}.
|
||||
|
||||
\begin{lstlisting}[caption=Исходный код программы №4, label=lst:program4]
|
||||
BEGIN
|
||||
x := 10;
|
||||
y := 10 - --x; /* Корректно: минус и декремент разделены пробелом */
|
||||
y := 10 ---x; /* Некорректно: три минуса подряд */
|
||||
END
|
||||
\end{lstlisting}
|
||||
|
||||
Результат запуска компилятора для данной программы представлен на Рис.~\ref{fig:result4}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{img/result4.png}
|
||||
\caption{Результат запуска компилятора для программы №4.}
|
||||
\label{fig:result4}
|
||||
\end{figure}
|
||||
|
||||
\subsection*{Программа №5}
|
||||
Исходный код программы представлен в листинге~\ref{lst:program5}.
|
||||
|
||||
\begin{lstlisting}[caption=Исходный код программы №5, label=lst:program5]
|
||||
BEGIN
|
||||
x := --x++
|
||||
END
|
||||
\end{lstlisting}
|
||||
|
||||
Результат запуска компилятора для данной программы представлен на Рис.~\ref{fig:result5}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{img/result5.png}
|
||||
\caption{Результат запуска компилятора для программы №5.}
|
||||
\label{fig:result5}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\newpage
|
||||
\section*{Заключение}
|
||||
\addcontentsline{toc}{section}{Заключение}
|
||||
В ходе выполнения лабораторной работы была успешно реализована поддержка операций инкремента и декремента в компиляторе языка MiLan. Были добавлены как префиксные (\texttt{++i}, \texttt{\textminus{}\textminus{}i}), так и постфиксные (\texttt{i++}, \texttt{i\textminus{}\textminus{}}) операторы с корректной семантикой их выполнения. Модификации затронули как лексический анализатор (добавление новых токенов \texttt{T\_INC} и \texttt{T\_DEC}), так и синтаксический анализатор, использующий метод рекурсивного спуска (расширение метода \texttt{factor()} для обработки новых конструкций).
|
||||
|
||||
Расширенная грамматика языка MiLan осталась контекстно-свободной и сохранила свойство LL(1), что позволило избежать значительных изменений в существующей архитектуре компилятора. Было добавлено лишь несколько новых правил в определение нетерминала \texttt{<factor>}.
|
||||
|
||||
Из достоинств реализации можно отметить минимальность вносимых изменений в существующую архитектуру компилятора и сохранение всех ранее реализованных функций. При этом реализация выполняет поставленные задачи, корректно обрабатывая возможные случаи использования операторов инкремента и декремента.
|
||||
|
||||
К недостаткам текущей реализации можно отнести отсутствие каких-либо оптимизаций при генерации команд виртуальной машины, что может приводить к избыточному количеству инструкций. Также в коде наблюдается некоторое дублирование логики между обработкой инкремента и декремента.
|
||||
|
||||
В качестве направлений масштабирования можно предложить добавление составных операторов присваивания (\texttt{+=}, \texttt{-=}, \texttt{*=}, \texttt{/=}), для которых генерируется схожая последовательность низкоуровневых команд. Также возможна реализация оптимизаций генерируемого кода, таких как устранение избыточных команд в простых случаях.
|
||||
|
||||
На выполнение лабораторной работы ушло около 6 часов. Работа была выполнена в среде разработки Visual Studio Code.
|
||||
|
||||
\newpage
|
||||
\section*{Список литературы}
|
||||
\addcontentsline{toc}{section}{Список литературы}
|
||||
|
||||
\vspace{-1.5cm}
|
||||
\begin{thebibliography}{0}
|
||||
\bibitem{vostrov}
|
||||
Востров, А.В. Курс лекций по дисциплине <<Математическая логика>>. URL \url{https://tema.spbstu.ru/compiler/} (дата обращения 01.05.2025 г.)
|
||||
\bibitem{aho}
|
||||
А. Ахо, М. Лам, Р. Сети, Дж. Ульман, Компиляторы: принципы, технологии и инструментарий, 2-е изд. М.: Вильямс, 2011.
|
||||
\bibitem{karpov}
|
||||
Ю.Г. Карпов, Теория и технология программирования. Основы построения трансляторов. СПб.: БХВ-Петербург, 2005.
|
||||
\end{thebibliography}
|
||||
|
||||
\end{document}
|
||||
BIN
lab5/img/tree.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
@@ -36,6 +36,7 @@
|
||||
\usepackage{xcolor} % цвета
|
||||
\usepackage{hyperref}% для гиперссылок
|
||||
\usepackage{enumitem} %для перечислений
|
||||
\usepackage{latexsym} %для символа \leadsto
|
||||
|
||||
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
|
||||
|
||||
@@ -156,7 +157,40 @@
|
||||
\item Назначить семантические действия части заданных продукций (например, генерация машинноориентированного кода).
|
||||
\end{itemize}
|
||||
|
||||
\textit{Грамматика в варианте 15 состоит из следующих продукций}:
|
||||
\textit{Исходная грамматика в варианте 15 состояла из следующих продукций}:
|
||||
\vspace{-0.3cm}
|
||||
\begin{verbatim}
|
||||
S -> B A b
|
||||
A -> a A B C | b B | a
|
||||
B -> b
|
||||
C -> c A
|
||||
\end{verbatim}
|
||||
|
||||
Однако, как будет показано в разделе математического описания, данная грамматика не является LL(1)-грамматикой из-за неоднозначности в выборе продукций для нетерминала $A$. Поэтому в работе используется модифицированная версия грамматики с удаленной продукцией $A \rightarrow a$.
|
||||
|
||||
|
||||
\newpage
|
||||
\section {Математическое описание}
|
||||
\subsection{Анализ исходной грамматики и её модификация}
|
||||
|
||||
Исходная грамматика варианта 15 содержала следующие продукции:
|
||||
\vspace{-0.3cm}
|
||||
\begin{verbatim}
|
||||
S -> B A b
|
||||
A -> a A B C | b B | a
|
||||
B -> b
|
||||
C -> c A
|
||||
\end{verbatim}
|
||||
|
||||
При попытке построения LL(1)-анализатора для данной грамматики обнаруживается неоднозначность в таблице синтаксического анализа. Конкретно, при рассмотрении нетерминала $A$ и входного терминала $a$ возникает конфликт между двумя продукциями:
|
||||
\begin{itemize}
|
||||
\item $A \rightarrow a$
|
||||
\item $A \rightarrow a A B C$
|
||||
\end{itemize}
|
||||
|
||||
Обе продукции начинаются с терминала $a$, что означает, что $a \in \texttt{FIRST}(a)$ и $a \in \texttt{FIRST}(a A B C)$. Следовательно, анализатор не может однозначно определить, какую продукцию применить при встрече символа $a$ на входе и нетерминала $A$ на вершине стека.
|
||||
|
||||
Для получения корректной LL(1)-грамматики была выполнена модификация: удалена продукция $A \rightarrow a$. Таким образом, итоговая грамматика, используемая в данной работе, имеет вид:
|
||||
\vspace{-0.3cm}
|
||||
\begin{verbatim}
|
||||
S -> B A b
|
||||
@@ -165,9 +199,8 @@
|
||||
C -> c A
|
||||
\end{verbatim}
|
||||
|
||||
Данная модификация устраняет неоднозначность и позволяет построить корректную таблицу синтаксического анализа для LL(1)-анализатора. Язык, порождаемый модифицированной грамматикой, остается достаточно выразительным для демонстрации принципов работы LL(1)-анализатора.
|
||||
|
||||
\newpage
|
||||
\section {Математическое описание}
|
||||
\subsection{Иерархия грамматик Хомского}
|
||||
Иерархия Хомского — классификация формальных языков и формальных грамматик, согласно которой они делятся на 4 типа по их условной сложности. На Рис.~\ref{fig:homsky} представлены все четыре типа грамматик (по типу продукций, из которых они состоят) и отношения между ними.
|
||||
|
||||
@@ -184,7 +217,7 @@
|
||||
|
||||
Контекстно-свободные грамматики являются частным случаем формальных грамматик, в которых левые части всех продукций содержат только один нетерминальный символ. Это означает, что все продукции имеют вид $A \rightarrow \beta$, где $A$ — нетерминальный символ, а $\beta$ — произвольная цепочка терминалов и нетерминалов.
|
||||
|
||||
Грамматика этого варианта лабораторной работы задаётся в виде следующего списка продукций:
|
||||
Модифицированная грамматика этого варианта лабораторной работы задаётся в виде следующего списка продукций:
|
||||
\begin{verbatim}
|
||||
S -> B A b
|
||||
A -> a A B C | b B
|
||||
@@ -196,6 +229,15 @@
|
||||
|
||||
Язык, порождаемый грамматикой, очевидно, не сложнее контекстно-свободного. Также, по продукции \texttt{A -> a A B C}, можно сделать вывод, что язык не является автоматным. Эта продукция является рекурсивной и порождает вложенную структуру, которая не может быть описана конечным автоматом.
|
||||
|
||||
Пример дерева вывода для цепочки языка, порождаемого грамматикой, представлен на Рис.~\ref{fig:tree}.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.7\linewidth]{img/tree.png}
|
||||
\caption{Дерево вывода для цепочки $b a b b b c b b b$.}
|
||||
\label{fig:tree}
|
||||
\end{figure}
|
||||
|
||||
|
||||
\subsection{LL(k)-анализаторы и LL(k)-грамматики}
|
||||
|
||||
@@ -297,6 +339,7 @@
|
||||
\label{tab:syntax_analysis}
|
||||
\end{table}
|
||||
|
||||
Если при распознавании цепочки синтаксический анализатор встречает ячейку с прочерком, то это означает, что входная цепочка не принадлежит языку, порождаемому грамматикой. В таком случае синтаксический анализатор завершает работу и возвращает сообщение об ошибке.
|
||||
|
||||
\subsection{Алгоритм разбора предложений}
|
||||
|
||||
@@ -347,11 +390,11 @@
|
||||
|
||||
Для этого продукции были сопоставлены со следующими инструкциями:
|
||||
\begin{itemize}
|
||||
\item $S \rightarrow B A b$ -- \texttt{iconst\_1}
|
||||
\item $A \rightarrow a A B C$ -- \texttt{iconst\_2 isub}
|
||||
\item $A \rightarrow b B$ -- \texttt{iconst\_1 iadd}
|
||||
\item $B \rightarrow b$ -- \texttt{iconst\_3 iadd}
|
||||
\item $C \rightarrow c A$ -- \texttt{iconst\_3 iadd}
|
||||
\item $S \rightarrow B A b$ $\leadsto$ \texttt{iconst\_1}
|
||||
\item $A \rightarrow a A B C$ $\leadsto$ \texttt{iconst\_2 isub}
|
||||
\item $A \rightarrow b B$ $\leadsto$ \texttt{iconst\_1 iadd}
|
||||
\item $B \rightarrow b$ $\leadsto$ \texttt{iconst\_3 iadd}
|
||||
\item $C \rightarrow c A$ $\leadsto$ \texttt{iconst\_3 iadd}
|
||||
\end{itemize}
|
||||
|
||||
Тогда при левом выводе цепочки \texttt{babbbcbbb}, будет сгенерирована следующая последовательность инструкций:
|
||||
@@ -1089,7 +1132,7 @@ FOLLOW(S) = {$}
|
||||
|
||||
Из достоинств выполнения лабораторной работы можно выделить, во-первых, возможность задания грамматики в отдельном текстовом файле, что позволяет легко изменять и расширять её без модификации программного кода. Во-вторых, программа автоматически проверяет, что введенная грамматика является LL(1)-грамматикой, и вычисляет множества FIRST и FOLLOW для всех нетерминалов, а также таблицу синтаксического анализа. В противном случае, программа выводит сообщение об ошибке, в котором указывается на конкретные правила грамматики, между выбором которых возникает неоднозначность. В третьих, программа позволяет легко задавать произвольные семантические действия в виде функций или объектов колбэков, которые будут вызываться при применении продукций.
|
||||
|
||||
К недостаткам текущей реализации можно отнести ограниченность словарного запаса, что сужает разнообразие генерируемых предложений. Также алгоритм генерации не контролирует длину предложений, что может приводить к избыточно длинным или коротким конструкциям.
|
||||
К недостаткам текущей реализации можно отнести то, что алгоритм генерации не контролирует длину предложений, что может приводить к избыточно длинным или коротким цепочкам.
|
||||
|
||||
Функционал программы несложно масштабировать. Например, несложно добавить функцию для визуализации дерева вывода распознаваемой цепочки, пусть даже в текстовом виде, в классе Grammar уже есть все необходимые для этого данные. Кроме того, класс Grammar может служить хорошей основой для создания полноценного LL(k) анализатора.
|
||||
|
||||
|
||||