Files
mathlogic/lab4/report/report.tex

961 lines
48 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\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}
\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}