Files
mathlogic/lab1/report.tex

650 lines
46 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[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{Лабораторная работа №1}\\
\large{<<Создание лексического анализатора>>}\\
\large{по дисциплине}\\
\large{<<Математическая логика и теория автоматов>>}\\
\large{Вариант 15}\\
% \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}{Введение}
Лабораторная №1 по дисциплине <<Математическая логика>> заключается в следующем. Необходимо написать программу, которая выполняет лексический анализ входного текста в
соответствии с вариантом задания и порождает таблицу лексем с указанием их типов и значений.
Также необходимо подготовить несколько вариантов программы в виде текста на входном языке.
Программа должна выдавать сообщения о наличие во входном тексте ошибок, которые
могут быть обнаружены на этапе лексического анализа.
Длина идентификатора и строковых констант ограничена 16 символами, только
латиница. Программа должна допускать наличие комментариев неограниченной длины
во входном файле.
\textit{Вариант 15}. Входной язык содержит арифметические выражения, разделенные символом |
(вертикальная полоса). Арифметические выражения состоят из идентификаторов,
комплексных чисел (в показательной форме), знака присваивания (:=), знаков операций +,
, *, /, \textasciicircum и круглых скобок.
\newpage
\section {Математическое описание}
\subsection{Структура транслятора}
Транслятор выполняет преобразование исходного текста $L$ на каком-либо языке в какую-либо структуру данных с сохранением смысла входного текста.
Транслятор можно разделить на три основные части (см. Рис.~\ref{fig:trs}):
\begin{itemize}
\item Лексический анализатор -- представляет исходную программу как последовательность лексем. Лексемы - минимальные единицы языка, имеющие смысл.
\item Распознаватель -- строит структуру исходного текста (например, в виде дерева) по полученной от лексического анализатора цепочке лексем.
\item Генератор -- использует построенную структуру для создания выходных данных, отражающих семантику входной цепочки.
\end{itemize}
\begin{figure}[h!]
\centering
\includegraphics[width=0.8\linewidth]{img/trs.png}
\caption{Структура транслятора.}
\label{fig:trs}
\end{figure}
В данной лабораторной работе необходимо написать программу лексический анализатор для заданного языка.
\subsection{Формальное определение лексического анализа}
Лексический анализ --- первая фаза трансляции программ на языках программирования, цель которой состоит в преобразовании входного текста программы в последовательность структурно значимых элементов --- \textit{лексем}.
Лексема --- это минимальная единица языка программирования, обладающая самостоятельным смыслом. В отличие от естественных языков, где лексемами выступают слова или словоформы, в языках программирования лексемами являются имена, ключевые (служебные) слова, числовые и строковые константы, а также операторы (в том числе составные, например, \verb|:=|).
Цели лексического анализа включают:
\begin{itemize}
\item определение класса каждой лексемы (например, идентификатор, число, строка, оператор и т.п.);
\item определение значения лексемы;
\item преобразование значений некоторых лексем (например, чисел) во внутреннее представление;
\item фильтрация и классификация символов для облегчения анализа.
\end{itemize}
В зависимости от класса, значение лексемы может быть преобразовано во внутреннее представление уже на этапе лексического анализа. Например, числа преобразуют в двоичное машинное представление, что обеспечивает более компактное хранение и проверку правильности диапазона на ранней стадии трансляции.
Перед лексическим анализом производится этап транслитерации, на котором каждому символу сопоставляется его класс. Типичные классы символов:
\begin{itemize}
\item \textit{буква} --- символы алфавита;
\item \textit{цифра} --- символы от \verb|0| до \verb|9|;
\item \textit{разделитель} --- пробел, перевод строки, табуляция и др.;
\item \textit{игнорируемый} --- символы, не несущие смысловой нагрузки (например, сигнальные коды);
\item \textit{запрещённый} --- символы, не входящие в алфавит языка;
\item \textit{прочие} --- остальные символы, не попавшие в предыдущие категории.
\end{itemize}
Для данного варианта лабораторной работы были выбраны следующие классы лексем:
\begin{enumerate}
\item \textbf{Идентификаторы} --- последовательности латинских букв, цифр и символов нижнего подчёркивания. Не могут начинаться с цифры. Максимальная длина идентификатора --- 16 символов. Примеры: \texttt{X}, \texttt{alpha1}, \texttt{\_private}.
\item \textbf{Комплексные числа в показательной форме} --- числа вида \texttt{aEb}, где \texttt{a} и \texttt{b} --- действительные числа (целые или с плавающей точкой), знак 'E' указывает на показатель степени. Примеры: \texttt{1.5E3}, \texttt{-2E-4}, \texttt{3.0E+2}.
\item \textbf{Оператор присваивания} --- составной символ \texttt{:=}, обозначающий операцию присваивания значения.
\item \textbf{Арифметические операторы} --- знаки \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\^} для выполнения соответствующих математических операций.
\item \textbf{Левая скобка} --- символ \texttt{(}, используемый для задания порядка вычислений в арифметических выражениях.
\item \textbf{Правая скобка} --- символ \texttt{)}, используемый для задания порядка вычислений в арифметических выражениях.
\item \textbf{Разделитель выражений} --- символ вертикальной черты \texttt{|}, отделяющий одно арифметическое выражение от другого.
\item \textbf{Комментарии} --- начинаются с символа \texttt{\#} и продолжаются до конца строки. Комментарии могут быть произвольной длины и содержать любые символы кроме переноса строки. Пример: \texttt{\# это комментарий}.
\item \textbf{Ошибки} — это случаи, когда входной текст нарушает правила формирования лексем. К таким ошибкам относятся идентификаторы, длина которых превышает 16 символов, идентификаторы, начинающиеся с цифры, и прочие неопознанные лексемы.
\end{enumerate}
\subsection{Формальная модель лексического анализатора}
Лексический анализатор (лексер) — это конечный автомат, который преобразует входную строку символов в последовательность токенов. Формально его можно описать следующим образом:
Пусть заданы:
\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{Регулярные выражения}
Регулярные выражения -- формальный язык шаблонов для поиска и выполнения манипуляций с подстроками в тексте. Регулярное выражение - это формула (pattern, шаблон), задающая правило поиска подстрок в потоке символов.
Все лексемы языка можно описать регулярными выражениями и для каждой задать
семантику (какую функцию вызывать, если распознана эта лексема например,
обратиться к таблице служебных слов и искать там, если нет, то ... ).
Классы лексем для этого варианта лабораторной работы определяются через регулярные выражения следующим образом.
\begin{enumerate}
\item \textbf{Идентификаторы.}
\begin{center}
$R_{\text{Identifier}}$ = [A-Za-z][A-Za-z\_0-9]\{0,15\}(?![A-Za-z\_0-9])
\end{center}
Первый символ — латинская буква. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора).
\item \textbf{Комплексные числа в показательной форме.}
\begin{center}
$R_{\text{Complex}}$ = [+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?E[+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?
\end{center}
Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число.
\item \textbf{Оператор присваивания.}
\begin{center}
$R_{\text{Assign}}$ = \textbackslash{}:=
\end{center}
\item \textbf{Арифметические операторы.}
\begin{center}
$R_{\text{ArithmeticOp}}$ = [+\textbackslash{}-\textbackslash{}*/\textbackslash{}\^{}]
\end{center}
\item \textbf{Левая скобка.}
\begin{center}
$R_{\text{LeftParen}}$ = \textbackslash{}(
\end{center}
\item \textbf{Правая скобка.}
\begin{center}
$R_{\text{RightParen}}$ = \textbackslash{})
\end{center}
\item \textbf{Разделитель выражений.}
\begin{center}
$R_{\text{Separator}}$ = \textbackslash{}\textbar
\end{center}
\item \textbf{Комментарии.}
\begin{center}
$R_{\text{Comment}}$ = \textbackslash{}\#.*
\end{center}
\end{enumerate}
В класс ошибок попадают все лексемы, неподошедшие ни под какой другой класс. Некорректная лексема считывается с помощью следующего регулярного выражения:
\begin{center}
$R_{\text{Error}}$ = .[\textasciicircum|()+\textbackslash{}-*/\textasciicircum{}:=\textbackslash{}s]*
\end{center}
Это регулярное выражение считывает как минимум один символ, чтобы анализ текста не завис. Затем регулярное выражение считывает все символы до первого пробела, арифметического оператора, левой или правой скобки, или оператора присваивания.
\subsection{Алгоритм лексического анализа}
Алгоритм лексического анализа состоит из следующих шагов:
\begin{enumerate}
\item \textbf{Инициализация:} указатель устанавливается на начало строки.
\item \textbf{Определение типа лексемы:} регулярные выражения, соответствующие различным типам лексем, перебираются в цикле до первого совпадения. Если регулярное выражение не найдено, то применяется регулярное выражение $R_{\text{Error}}$.
\item \textbf{Извлечение лексемы:} с помощью регулярного выражения лексема извлекается из строки и сохраняется в списке лексем с указанием типа лексемы.
\item \textbf{Вычисление значения лексемы:} значение каждой лексемы определяется посредством вызова функции обработчика, соответствующей типу лексемы. Функция обработчик получает на вход текст лексемы и возвращает некоторый объект, представляющий её значение.
\item \textbf{Сохранение лексемы:} текст лексемы, её тип и значение сохраняются в итоговый список лексем.
\item \textbf{Сдвиг указателя:} указатель сдвигается на следующий непросмотренный символ.
\item \textbf{Завершение анализа:} алгоритм завершается, когда указатель сдвигается за пределы строки. То есть, когда вся строка разобрана на лексемы.
\end{enumerate}
Сложность такого алгортма без учёта сложности функций вычисления значений лексем -- $O(n)$, где $n$ -- количество символов в исходном тексте программы.
\subsection{Синтаксическая диаграмма}
Синтаксическая диаграмма реализованной программы представлена на Рис.~\ref{fig:diag}.
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\linewidth]{img/diag.png}
\caption{Синтаксическая диаграмма реализованной программы.}
\label{fig:diag}
\end{figure}
\newpage
\phantom{text}
\newpage
\section{Особенности реализации}
\subsection{Общая структура программы}
Программа состоит из двух файлов:
\begin{itemize}
\item \texttt{lexer.py} -- содержит небольшую библиотеку для создания лексических анализаторов для произвольных языков. Файл содержит классы \texttt{Lexem}, \texttt{LexemeType} и \texttt{Lexer}.
\item \texttt{main.py} -- файл содержит список типов лексем и их регулярных выражений -- (\texttt{LEXEME\_TYPES}), а также два обработчика для вычисления значений лексем -- \texttt{exp\_form\_to\_complex} и \texttt{IdentifierMapper}. Также в файле определены функции: \texttt{analyze\_and\_print\_table} -- запускает лексический анализ и выводит результаты в консоль в виде таблицы, \texttt{main} -- обрабатывает пользовательский ввод.
\end{itemize}
\subsection{Класс Lexem}
Класс Lexem это простой датакласс для представления лексем. Код определения класса представлен в листинге~\ref{lst:Lexem}. В классе есть всего три поля строкового типа:
\texttt{text} -- текст лексемы, \texttt{type\_name} -- название типа лексемы, \texttt{value} -- значение лексемы.
\begin{lstlisting}[caption={Определение класса Lexem.}, label={lst:Lexem}]
@dataclass
class Lexem:
text: str
type_name: str
value: str
\end{lstlisting}
\subsection{Класс LexemeType}
Класс LexemeType представляет собой тип лексемы. Код определения класса представлен в листинге~\ref{lst:LexemeType}. В классе всего три поля:
\texttt{name} -- строка с названием типа лексемы, \texttt{regex} -- регулярное выражение, соответствующее типу лексемы, \texttt{value\_func} -- функция для вычисления значения лексемы.
В классе определён единственный метод \texttt{consume}. Он принимает два параметра: \texttt{self} -- ссылку на объект класса, и строку \texttt{text} с текстом программы, из которого нужно попытаться извлечь лексему. Возвращает кортеж из двух элементов: объект класса Lexem, если лексему удалось извлечь, иначе None, и строку с текстом программы, оставшимся после извлечения лексемы.
\begin{lstlisting}[caption={Определение класса LexemeType.}, label={lst:LexemeType}]
class LexemeType:
def __init__(
self,
name: str,
pattern: str,
value_func: Callable[[str], str] = lambda _: "",
):
self.name = name
self.regex = re.compile(r"\s*(" + pattern + ")")
self.value_func = value_func
def consume(self, text: str) -> tuple[Lexem | None, str]:
match = self.regex.match(text)
if match:
lexeme_text = match.group(1)
value = self.value_func(lexeme_text)
rest = text[match.end() :]
return Lexem(lexeme_text, self.name, value), rest
return None, text
\end{lstlisting}
\subsection{Класс Lexer}
Класс Lexer представляет собой лексический анализатор. Код определения класса представлен в листинге~\ref{lst:Lexer}. В классе определены следующие поля:
\begin{itemize}
\item \texttt{lexeme\_types} -- список объектов класса \texttt{LexemeType}
\item \texttt{error\_regex} -- строка, содержащая регулярное выражение для захвата ошибочных лексем
\item \texttt{skip\_types} -- список строк с названиями типов лексем, которые следует пропускать при анализе (например, комментарии)
\end{itemize}
В классе определено два метода: \texttt{analyze} и вспомогательный \texttt{\_consume\_error}.
Метод \texttt{analyze} выполняет лексический разбор входного текста. Он принимает строку \texttt{text}, содержащую текст программы, и возвращает список объектов типа \texttt{Lexem}. Метод поочерёдно применяет каждый тип лексемы из \texttt{lexeme\_types}, пытаясь извлечь очередную лексему. Если хотя бы один тип лексемы успешно извлекает лексему, она добавляется в результат (если её тип не входит в \texttt{skip\_types}), а оставшийся текст анализируется далее. Если ни одна лексема не подошла, вызывается метод \texttt{\_consume\_error} для обработки ошибки.
Метод \texttt{\_consume\_error} используется для обработки ситуаций, когда входной фрагмент не соответствует ни одному из допустимых шаблонов. Он использует \texttt{error\_regex} для поиска ошибочной последовательности символов, сообщает об ошибке в консоль и создаёт лексему с типом \texttt{"ERROR"}. Возвращает эту ошибочную лексему и оставшийся текст.
\begin{lstlisting}[caption={Определение класса Lexer.}, label={lst:Lexer}]
class Lexer:
def __init__(
self,
lexeme_types: Iterable[LexemeType],
error_regex: str,
skip_types: Iterable[str] = [],
):
self.lexeme_types = lexeme_types
self.skip_types = skip_types
self.error_regex = re.compile(r"\s*(" + error_regex + ")")
def analyze(self, text: str) -> list[Lexem]:
lexems: list[Lexem] = []
while text.strip():
for lex_type in self.lexeme_types:
lexem, new_text = lex_type.consume(text)
if lexem:
if lexem.type_name not in self.skip_types:
lexems.append(lexem)
text = new_text
break
else:
error_lexeme, text = self._consume_error(text)
return lexems
def _consume_error(self, text: str) -> tuple[Lexem, str]:
match = self.error_regex.match(text)
err_text = match.group(1) if match else text.strip()
print(f"Недопустимая лексема: {err_text}")
rest = text[match.end() :] if match else ""
return Lexem(err_text, "ERROR", ""), rest
\end{lstlisting}
\subsection{Класс \texttt{IdentifierMapper}}
Класс \texttt{IdentifierMapper} используется для сопоставления идентификаторов с уникальными значениями. Код класса приведён в листинге~\ref{lst:IdentifierMapper}. В классе определены два поля: \texttt{id\_table: dict[str, str]}~--- таблица, содержащая отображения идентификаторов на строки с их порядковыми номерами, и \texttt{counter: int}~--- счётчик, увеличиваемый при каждом новом идентификаторе.
Метод \texttt{\_\_call\_\_(self, lex\_text: str)~$\rightarrow$~str} проверяет, был ли ранее встречен переданный идентификатор. Если нет~--- добавляет его в таблицу, присваивая уникальный номер, и возвращает соответствующее строковое представление.
\begin{lstlisting}[caption={Класс IdentifierMapper.}, label={lst:IdentifierMapper}]
class IdentifierMapper:
def __init__(self):
self.id_table = {}
self.counter = 0
def __call__(self, lex_text: str) -> str:
if lex_text not in self.id_table:
self.id_table[lex_text] = f"{lex_text} : {self.counter}"
self.counter += 1
return self.id_table[lex_text]
\end{lstlisting}
\subsection{Функция \texttt{exp\_form\_to\_complex}}
Функция \texttt{exp\_form\_to\_complex}, показанная в листинге~\ref{lst:expToComplex}, принимает один параметр \texttt{exp\_str: str}~--- строку с числом в экспоненциальной форме. Возвращает строку \texttt{str}, представляющую это число в виде комплексного числа в алгебраической форме \( a + i \cdot b \), где \( a = r \cdot \cos(\varphi) \), \( b = r \cdot \sin(\varphi) \).
\begin{lstlisting}[caption={Функция exp\_form\_to\_complex.}, label={lst:expToComplex}]
def exp_form_to_complex(exp_str: str) -> str:
base, exponent = exp_str.split("E")
r = float(base)
phi = float(exponent)
a = r * math.cos(phi)
b = r * math.sin(phi)
return f"{a:.2f} + i * {b:.2f}"
\end{lstlisting}
\subsection{Список \texttt{LEXEME\_TYPES}}
Словарь \texttt{LEXEME\_TYPES} (листинг~\ref{lst:LexemeTypes}) содержит определения всех типов лексем, используемых в лексическом анализе. Ключами являются строковые названия типов, значениями~--- экземпляры класса \texttt{LexemeType}. Каждая лексема описывается с помощью регулярного выражения и, при необходимости, функцией обработки значения (например, \texttt{IdentifierMapper} или \texttt{exp\_form\_to\_complex}).
\begin{lstlisting}[caption={Определение лексем в LEXEME\_TYPES.}, label={lst:LexemeTypes}]
LEXEME_TYPES: dict[str, LexemeType] = {
"IDENTIFIER": LexemeType(
"IDENTIFIER", r"[A-Za-z][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper()
),
"COMPLEX": LexemeType(
"COMPLEX",
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?",
exp_form_to_complex,
),
"ASSIGN": LexemeType("ASSIGN", r"\:="),
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
"LPAREN": LexemeType("LPAREN", r"\("),
"RPAREN": LexemeType("RPAREN", r"\)"),
"SEPARATOR": LexemeType("SEPARATOR", r"\|"),
"COMMENT": LexemeType("COMMENT", r"\#.*"),
}
\end{lstlisting}
\subsection{Функция \texttt{analyze\_and\_print\_table}}
Функция \texttt{analyze\_and\_print\_table}, представленная в листинге~\ref{lst:AnalyzePrint}, принимает один параметр \texttt{code: str}~--- строку с текстом программы. Она создаёт объект \texttt{Lexer}, выполняет анализ текста на лексемы и выводит результат в виде таблицы, используя библиотеку \texttt{PrettyTable}. Каждая строка таблицы содержит текст лексемы, её тип и вычисленное значение.
\begin{lstlisting}[caption={Функция analyze\_and\_print\_table.}, label={lst:AnalyzePrint}]
def analyze_and_print_table(code: str):
lexer = Lexer(LEXEME_TYPES.values(), ERROR_REGEX, skip_types=["COMMENT"])
lexemes = lexer.analyze(code)
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])
for l in lexemes:
table.add_row([l.text, l.type_name, l.value])
print(table)
print()
LEXEME_TYPES["IDENTIFIER"].value_func = IdentifierMapper()
\end{lstlisting}
\subsection{Функция \texttt{main}}
Функция \texttt{main} (листинг~\ref{lst:Main}) организует интерактивный интерфейс пользователя. Не принимает параметров. Пользователь вводит строку \texttt{file\_name}, содержащую путь к файлу. Если файл существует, его содержимое читается и передаётся в функцию \texttt{analyze\_and\_print\_table}. При вводе строки \texttt{"exit"} программа завершает выполнение. Если файл не найден, пользователю выводится сообщение об ошибке.
\begin{lstlisting}[caption={Функция main.}, label={lst:Main}]
def main():
while True:
file_name = input(
"Введите название файла для анализа (или 'exit' для выхода): "
)
if file_name.lower() == "exit":
print("Завершаю программу.")
break
if not os.path.isfile(file_name):
print(f"Файл '{file_name}' не найден. Попробуйте снова.")
continue
with open(file_name, "r", encoding="utf-8") as file:
code = file.read()
analyze_and_print_table(code)
\end{lstlisting}
\newpage
\section{Результаты работы программы}
На Рис.~\ref{fig:result1}-\ref{fig:result3} представлены результаты работы лексического анализатора, запущенного на файлах с тремя различными программами (см. листинги~\ref{lst:prog1}-\ref{lst:prog3}).
\begin{lstlisting}[caption={Программа 1.}, label={lst:prog1}]
alpha:=2E3.1415|beta:=4E0|theta:=alpha+beta+-3E-1.57
| dzeta := alpha + (2.1E2.1 - 22E2)
\end{lstlisting}
\begin{figure}[h!]
\centering
\includegraphics[width=0.6\linewidth]{img/result1.png}
\caption{Результат работы лексического анализатора на программе 1 (листинг~\ref{lst:prog1}).}
\label{fig:result1}
\end{figure}
\begin{lstlisting}[caption={Программа 2.}, label={lst:prog2}]
abc + +100E-3.1415
# some_id_ :=:= 100
ThisIdentifierIsTooLong :=:=
\end{lstlisting}
\begin{figure}[h!]
\centering
\includegraphics[width=0.8\linewidth]{img/result2.png}
\caption{Результат работы лексического анализатора на программе 2 (листинг~\ref{lst:prog2}).}
\label{fig:result2}
\end{figure}
\newpage
\begin{lstlisting}[caption={Программа 3.}, label={lst:prog3}]
x := 2.5E+3 + y1 |
z := 3.1E+ | # число с ошибкой
x := x + -2.5E+3 |
1_first # идентификатор с цифры
\end{lstlisting}
\begin{figure}[h!]
\centering
\includegraphics[width=0.7\linewidth]{img/result3.png}
\caption{Результат работы лексического анализатора на программе 3 (листинг~\ref{lst:prog3}).}
\label{fig:result3}
\end{figure}
\newpage
\begin{figure}[h!]
\centering
\includegraphics[width=0.9\linewidth]{img/wrong.png}
\caption{Реакция программы на некорректный пользовательский ввод.}
\label{fig:wrong}
\end{figure}
На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод.
\newpage
\section*{Заключение}
\addcontentsline{toc}{section}{Заключение}
В ходе выполнения лабораторной работы был разработан лексический анализатор для языка, поддерживающего идентификаторы до 16 символов, комплексные числа в экспоненциальной форме, оператор присваивания (\texttt{:=}), арифметические операторы (\texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\textasciicircum}), скобки (\texttt{(}, \texttt{)}), разделитель выражений (\texttt{|}), комментарии (\texttt{\#}). Для каждого типа лексем было разработано отдельное регулярное выражение. Предложенная программная реализация лексического анализатора выводит в консоль таблицу лексем с указанием их типов и значений. Также программа отдельно выводит сообщения об ошибочных лексемах.
Данный язык является автоматным, так как представим в виде синтаксической диаграммы. Автоматные грамматики — самые простые из формальных грамматик. Они являются подмножеством контекстно-свободных грамматик.
Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы лексического анализатора вынесена в отдельный класс \texttt{Lexer}. Логика работы с типами лексем в класс \texttt{LexemeType}. Также в качестве достоинства можно отметить удобочитаемый вывод таблиц в консоли с помощью библиотеки \texttt{PrettyTable}.
Можно выделить два недостатка текущей реализации. Во-первых, классы \texttt{Lexer} и \texttt{LexemeType} не поддерживают лексемы, в состав которых входят пробельные символы. Это ограничение также касается и лексем из класса <<Ошибка>>, поэтому предложенный лексический анализатор не может обнаружить, например, следующую ошибку: <<\texttt{:= :=}>> -- между повторяющимися операторами присваивания находится пробельный символ. Во-вторых, алгоритм, лежащий в основе реализации лексического анализатора, не предусматривает анализ структуры входного текста, поэтому способен обнаружить ошибки только на уровне отдельных лексем.
Функционал программы несложно масштабировать. Классы \texttt{Lexer}, \texttt{LexemeType} и \texttt{Lexem} получились достаточно универсальными. Для того, чтобы добавить обработку дополнительных типов лексем, достаточно расширить словарь \texttt{LEXEME\_TYPES}. Кроме того, класс \texttt{LexemeType} позволяет использовать любые \texttt{Callable} объекты в качестве обработчиков для получения значений лексем. Таким образом код, написанный для решения конкретного варианта лабораторной работы, легко можно адаптировать для решения других вариантов.
На выполнение лабораторной работы ушло около 12 часов. Работа была выполнена в среде разработки Visual Studio Code. Программа написана на Python версии 3.13.
\newpage
\section*{Список литературы}
\addcontentsline{toc}{section}{Список литературы}
\vspace{-1.5cm}
\begin{thebibliography}{0}
\bibitem{vostrov}
Востров, А.В. Курс лекций по дисциплине <<Математическая логика>>. URL \url{https://tema.spbstu.ru/compiler/} (дата обращения 01.04.2025 г.)
\bibitem{lutz}
Лутц, М. Изучаем Python. 5-е изд. / М. Лутц. — СПб.: Питер, 2019. — 1216 с.
\bibitem{friedl}
Фридл, Дж. Регулярные выражения = Mastering Regular Expressions / Дж. Фридл. — СПб.: Питер, 2001. — 352 с. — (Библиотека программиста).
\end{thebibliography}
\end{document}