15 Commits

Author SHA1 Message Date
6a95dc6c9d Добавил пример с --x++ 2025-05-30 13:17:44 +03:00
78dddbc047 Добавил упоминание КС грамматики и рекурсивного спука в заключении 2025-05-30 13:17:17 +03:00
e0211cb6c5 Выделил цветом новый продукции 2025-05-30 13:06:01 +03:00
d61b1b7a2d заменил -- на textminus 2025-05-30 12:50:32 +03:00
c001ccb12a лаб 4 2025-05-28 14:19:58 +03:00
fdd30a8854 Инкремент и декремент в компиляторе 2025-05-28 12:26:04 +03:00
35cf9b5ed4 Добавил инкремент и декремент в лексер 2025-05-28 12:07:32 +03:00
e3918eb442 Исправил ошибки в исходной грамматике 2025-05-27 22:26:20 +03:00
207b428ad9 Лаб 4 - Матописание 2025-05-27 13:32:11 +03:00
09da4c5c62 Правки: добавил про модификацию грамматики 2025-05-25 15:31:50 +03:00
cc5fbb2163 Убрал следы другой лабы 2025-05-25 15:22:39 +03:00
037a4cd8a0 Правки: семантические действия через стрелочку 2025-05-25 15:18:12 +03:00
b64f7a3205 Правки: значение прочерка 2025-05-25 15:11:01 +03:00
b835502ed4 Дерево разбора 2025-05-25 15:10:41 +03:00
796ac33829 remove .o files 2025-05-22 15:39:26 +03:00
37 changed files with 1121 additions and 16 deletions

1
lab4/.gitignore vendored
View File

@@ -1,4 +1,5 @@
*.zip *.zip
*.cmil *.cmil
*.o
cmilan/src/cmilan.exe cmilan/src/cmilan.exe
!cmilan/doc/cmilan.pdf !cmilan/doc/cmilan.pdf

Binary file not shown.

Binary file not shown.

View File

@@ -155,7 +155,8 @@ void Parser::factor()
{ {
/* /*
Множитель описывается следующими правилами: Множитель описывается следующими правилами:
<factor> -> number | identifier | -<factor> | (<expression>) | READ <factor> -> number | identifier | -<factor> | (<expression>) | READ
| ++ identifier | -- identifier | identifier++ | identifier--
*/ */
if(see(T_NUMBER)) { if(see(T_NUMBER)) {
int value = scanner_->getIntValue(); int value = scanner_->getIntValue();
@@ -168,7 +169,29 @@ void Parser::factor()
next(); next();
codegen_->emit(LOAD, varAddress); 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) { else if(see(T_ADDOP) && scanner_->getArithmeticValue() == A_MINUS) {
next(); next();
factor(); factor();

Binary file not shown.

View File

@@ -28,6 +28,8 @@ static const char * tokenNames_[] = {
"'('", "'('",
"')'", "')'",
"';'", "';'",
"'++'",
"'--'",
}; };
void Scanner::nextToken() void Scanner::nextToken()
@@ -185,15 +187,31 @@ void Scanner::nextToken()
//Знаки операций. Для "+"/"-" получим лексему операции типа сложнения, и соответствующую операцию. //Знаки операций. Для "+"/"-" получим лексему операции типа сложнения, и соответствующую операцию.
//для "*" - лексему операции типа умножения //для "*" - лексему операции типа умножения
case '+': case '+':
token_ = T_ADDOP;
arithmeticValue_ = A_PLUS;
nextChar(); nextChar();
// Ищем оператор инкремента
if(ch_ == '+') {
token_ = T_INC;
nextChar();
}
else {
token_ = T_ADDOP;
arithmeticValue_ = A_PLUS;
}
break; break;
case '-': case '-':
token_ = T_ADDOP;
arithmeticValue_ = A_MINUS;
nextChar(); nextChar();
// Ищем оператор декремента
if(ch_ == '-') {
token_ = T_DEC;
nextChar();
}
else {
token_ = T_ADDOP;
arithmeticValue_ = A_MINUS;
}
break; break;
case '*': case '*':

View File

@@ -29,7 +29,9 @@ enum Token {
T_CMP, // Сводная лексема для операторов отношения T_CMP, // Сводная лексема для операторов отношения
T_LPAREN, // Открывающая скобка T_LPAREN, // Открывающая скобка
T_RPAREN, // Закрывающая скобка T_RPAREN, // Закрывающая скобка
T_SEMICOLON // ";" T_SEMICOLON, // ";"
T_INC, // Оператор инкремента
T_DEC // Оператор декремента
}; };
// Функция tokenToString возвращает описание лексемы. // Функция tokenToString возвращает описание лексемы.

Binary file not shown.

View File

@@ -0,0 +1,7 @@
BEGIN
x := 0;
write(x++);
write(x);
write(++x);
write(x)
END

View File

@@ -0,0 +1,6 @@
BEGIN
i := 1;
j := 2;
IF i < --j THEN WRITE(100) ELSE WRITE(-100) FI
END

View 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

View File

@@ -0,0 +1,5 @@
BEGIN
x := 10;
y := 10 - --x; /* Корректно: минус и декремент разделены пробелом */
y := 10 ---x; /* Некорректно: три минуса подряд */
END

View File

@@ -0,0 +1,3 @@
BEGIN
x := --x++
END

5
lab4/report/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
**/*
!.gitignore
!report.tex
!img
!img/**

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

BIN
lab4/report/img/result1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
lab4/report/img/result2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
lab4/report/img/result3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
lab4/report/img/result4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
lab4/report/img/result5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
lab4/report/img/scheme.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

979
lab4/report/report.tex Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -36,6 +36,7 @@
\usepackage{xcolor} % цвета \usepackage{xcolor} % цвета
\usepackage{hyperref}% для гиперссылок \usepackage{hyperref}% для гиперссылок
\usepackage{enumitem} %для перечислений \usepackage{enumitem} %для перечислений
\usepackage{latexsym} %для символа \leadsto
\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}} \newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}}
@@ -156,7 +157,40 @@
\item Назначить семантические действия части заданных продукций (например, генерация машинноориентированного кода). \item Назначить семантические действия части заданных продукций (например, генерация машинноориентированного кода).
\end{itemize} \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} \vspace{-0.3cm}
\begin{verbatim} \begin{verbatim}
S -> B A b S -> B A b
@@ -165,9 +199,8 @@
C -> c A C -> c A
\end{verbatim} \end{verbatim}
Данная модификация устраняет неоднозначность и позволяет построить корректную таблицу синтаксического анализа для LL(1)-анализатора. Язык, порождаемый модифицированной грамматикой, остается достаточно выразительным для демонстрации принципов работы LL(1)-анализатора.
\newpage
\section {Математическое описание}
\subsection{Иерархия грамматик Хомского} \subsection{Иерархия грамматик Хомского}
Иерархия Хомского — классификация формальных языков и формальных грамматик, согласно которой они делятся на 4 типа по их условной сложности. На Рис.~\ref{fig:homsky} представлены все четыре типа грамматик (по типу продукций, из которых они состоят) и отношения между ними. Иерархия Хомского — классификация формальных языков и формальных грамматик, согласно которой они делятся на 4 типа по их условной сложности. На Рис.~\ref{fig:homsky} представлены все четыре типа грамматик (по типу продукций, из которых они состоят) и отношения между ними.
@@ -184,7 +217,7 @@
Контекстно-свободные грамматики являются частным случаем формальных грамматик, в которых левые части всех продукций содержат только один нетерминальный символ. Это означает, что все продукции имеют вид $A \rightarrow \beta$, где $A$ — нетерминальный символ, а $\beta$ — произвольная цепочка терминалов и нетерминалов. Контекстно-свободные грамматики являются частным случаем формальных грамматик, в которых левые части всех продукций содержат только один нетерминальный символ. Это означает, что все продукции имеют вид $A \rightarrow \beta$, где $A$ — нетерминальный символ, а $\beta$ — произвольная цепочка терминалов и нетерминалов.
Грамматика этого варианта лабораторной работы задаётся в виде следующего списка продукций: Модифицированная грамматика этого варианта лабораторной работы задаётся в виде следующего списка продукций:
\begin{verbatim} \begin{verbatim}
S -> B A b S -> B A b
A -> a A B C | b B A -> a A B C | b B
@@ -196,6 +229,15 @@
Язык, порождаемый грамматикой, очевидно, не сложнее контекстно-свободного. Также, по продукции \texttt{A -> a A B C}, можно сделать вывод, что язык не является автоматным. Эта продукция является рекурсивной и порождает вложенную структуру, которая не может быть описана конечным автоматом. Язык, порождаемый грамматикой, очевидно, не сложнее контекстно-свободного. Также, по продукции \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)-грамматики} \subsection{LL(k)-анализаторы и LL(k)-грамматики}
@@ -297,6 +339,7 @@
\label{tab:syntax_analysis} \label{tab:syntax_analysis}
\end{table} \end{table}
Если при распознавании цепочки синтаксический анализатор встречает ячейку с прочерком, то это означает, что входная цепочка не принадлежит языку, порождаемому грамматикой. В таком случае синтаксический анализатор завершает работу и возвращает сообщение об ошибке.
\subsection{Алгоритм разбора предложений} \subsection{Алгоритм разбора предложений}
@@ -347,11 +390,11 @@
Для этого продукции были сопоставлены со следующими инструкциями: Для этого продукции были сопоставлены со следующими инструкциями:
\begin{itemize} \begin{itemize}
\item $S \rightarrow B A b$ -- \texttt{iconst\_1} \item $S \rightarrow B A b$ $\leadsto$ \texttt{iconst\_1}
\item $A \rightarrow a A B C$ -- \texttt{iconst\_2 isub} \item $A \rightarrow a A B C$ $\leadsto$ \texttt{iconst\_2 isub}
\item $A \rightarrow b B$ -- \texttt{iconst\_1 iadd} \item $A \rightarrow b B$ $\leadsto$ \texttt{iconst\_1 iadd}
\item $B \rightarrow b$ -- \texttt{iconst\_3 iadd} \item $B \rightarrow b$ $\leadsto$ \texttt{iconst\_3 iadd}
\item $C \rightarrow c A$ -- \texttt{iconst\_3 iadd} \item $C \rightarrow c A$ $\leadsto$ \texttt{iconst\_3 iadd}
\end{itemize} \end{itemize}
Тогда при левом выводе цепочки \texttt{babbbcbbb}, будет сгенерирована следующая последовательность инструкций: Тогда при левом выводе цепочки \texttt{babbbcbbb}, будет сгенерирована следующая последовательность инструкций:
@@ -1089,7 +1132,7 @@ FOLLOW(S) = {$}
Из достоинств выполнения лабораторной работы можно выделить, во-первых, возможность задания грамматики в отдельном текстовом файле, что позволяет легко изменять и расширять её без модификации программного кода. Во-вторых, программа автоматически проверяет, что введенная грамматика является LL(1)-грамматикой, и вычисляет множества FIRST и FOLLOW для всех нетерминалов, а также таблицу синтаксического анализа. В противном случае, программа выводит сообщение об ошибке, в котором указывается на конкретные правила грамматики, между выбором которых возникает неоднозначность. В третьих, программа позволяет легко задавать произвольные семантические действия в виде функций или объектов колбэков, которые будут вызываться при применении продукций. Из достоинств выполнения лабораторной работы можно выделить, во-первых, возможность задания грамматики в отдельном текстовом файле, что позволяет легко изменять и расширять её без модификации программного кода. Во-вторых, программа автоматически проверяет, что введенная грамматика является LL(1)-грамматикой, и вычисляет множества FIRST и FOLLOW для всех нетерминалов, а также таблицу синтаксического анализа. В противном случае, программа выводит сообщение об ошибке, в котором указывается на конкретные правила грамматики, между выбором которых возникает неоднозначность. В третьих, программа позволяет легко задавать произвольные семантические действия в виде функций или объектов колбэков, которые будут вызываться при применении продукций.
К недостаткам текущей реализации можно отнести ограниченность словарного запаса, что сужает разнообразие генерируемых предложений. Также алгоритм генерации не контролирует длину предложений, что может приводить к избыточно длинным или коротким конструкциям. К недостаткам текущей реализации можно отнести то, что алгоритм генерации не контролирует длину предложений, что может приводить к избыточно длинным или коротким цепочкам.
Функционал программы несложно масштабировать. Например, несложно добавить функцию для визуализации дерева вывода распознаваемой цепочки, пусть даже в текстовом виде, в классе Grammar уже есть все необходимые для этого данные. Кроме того, класс Grammar может служить хорошей основой для создания полноценного LL(k) анализатора. Функционал программы несложно масштабировать. Например, несложно добавить функцию для визуализации дерева вывода распознаваемой цепочки, пусть даже в текстовом виде, в классе Grammar уже есть все необходимые для этого данные. Кроме того, класс Grammar может служить хорошей основой для создания полноценного LL(k) анализатора.