961 lines
48 KiB
TeX
961 lines
48 KiB
TeX
\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[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}
|
||
<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>
|
||
| <ident> ‘++’
|
||
| <ident> ‘--’
|
||
| ‘++’ <ident>
|
||
| ‘--’ <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} |