Правки по lab1

This commit is contained in:
2025-04-29 13:13:11 +03:00
parent 3963d304ec
commit 20782a2473
7 changed files with 75 additions and 40 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -34,8 +34,15 @@ class LexemeType:
class Lexer: class Lexer:
def __init__(self, lexeme_types: Iterable[LexemeType]): def __init__(
self,
lexeme_types: Iterable[LexemeType],
error_regex: str,
skip_types: Iterable[str] = [],
):
self.lexeme_types = lexeme_types 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]: def analyze(self, text: str) -> list[Lexem]:
lexems: list[Lexem] = [] lexems: list[Lexem] = []
@@ -43,16 +50,16 @@ class Lexer:
for lex_type in self.lexeme_types: for lex_type in self.lexeme_types:
lexem, new_text = lex_type.consume(text) lexem, new_text = lex_type.consume(text)
if lexem: if lexem:
lexems.append(lexem) if lexem.type_name not in self.skip_types:
lexems.append(lexem)
text = new_text text = new_text
break break
else: else:
error_lexeme, text = self._consume_error(text) error_lexeme, text = self._consume_error(text)
lexems.append(error_lexeme)
return lexems return lexems
def _consume_error(self, text: str) -> tuple[Lexem, str]: def _consume_error(self, text: str) -> tuple[Lexem, str]:
match = re.match(r"\s*(\S+)", text) match = self.error_regex.match(text)
err_text = match.group(1) if match else text.strip() err_text = match.group(1) if match else text.strip()
print(f"Недопустимая лексема: {err_text}") print(f"Недопустимая лексема: {err_text}")
rest = text[match.end() :] if match else "" rest = text[match.end() :] if match else ""

View File

@@ -2,9 +2,8 @@ import math
import os import os
from typing import Callable from typing import Callable
from prettytable import PrettyTable
from lexer import LexemeType, Lexer from lexer import LexemeType, Lexer
from prettytable import PrettyTable
class IdentifierMapper: class IdentifierMapper:
@@ -35,30 +34,34 @@ def exp_form_to_complex(exp_str: str) -> str:
LEXEME_TYPES: dict[str, LexemeType] = { LEXEME_TYPES: dict[str, LexemeType] = {
# 1. Идентификаторы # 1. Идентификаторы
"IDENTIFIER": LexemeType( "IDENTIFIER": LexemeType(
"IDENTIFIER", r"[A-Za-z_][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper() "IDENTIFIER", r"[A-Za-z][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper()
), ),
# 2. Комплексные числа в показательной форме (на самом деле — числа в экспоненциальной форме) # 2. Комплексные числа в показательной форме (на самом деле — числа в экспоненциальной форме)
"COMPLEX": LexemeType( "COMPLEX": LexemeType(
# "COMPLEX", r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?!E)", exp_form_to_complex # "COMPLEX", r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?!E)", exp_form_to_complex
"COMPLEX", "COMPLEX",
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?(?!E)", r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?",
exp_form_to_complex, exp_form_to_complex,
), ),
# 3. Оператор присваивания := # 3. Оператор присваивания :=
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"), "ASSIGN": LexemeType("ASSIGN", r"\:="),
# 4. Арифметические операторы # 4. Арифметические операторы
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"), "ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
# 5. Скобки # 5. Левая скобка
"PAREN": LexemeType("PAREN", r"[()]"), "LPAREN": LexemeType("LPAREN", r"\("),
# 6. Разделитель выражений | # 6. Правая скобка
"RPAREN": LexemeType("RPAREN", r"\)"),
# 7. Разделитель выражений |
"SEPARATOR": LexemeType("SEPARATOR", r"\|"), "SEPARATOR": LexemeType("SEPARATOR", r"\|"),
# 7. Комментарии от # до конца строки # 8. Комментарии от # до конца строки
"COMMENT": LexemeType("COMMENT", r"\#.*"), "COMMENT": LexemeType("COMMENT", r"\#.*"),
} }
ERROR_REGEX = r".[^|()+\-*/^:=\s]*"
def analyze_and_print_table(code: str): def analyze_and_print_table(code: str):
lexer = Lexer(LEXEME_TYPES.values()) lexer = Lexer(LEXEME_TYPES.values(), ERROR_REGEX, skip_types=["COMMENT"])
lexemes = lexer.analyze(code) lexemes = lexer.analyze(code)
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"]) table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])

View File

@@ -223,8 +223,10 @@
\item \textbf{Оператор присваивания} --- составной символ \texttt{:=}, обозначающий операцию присваивания значения. \item \textbf{Оператор присваивания} --- составной символ \texttt{:=}, обозначающий операцию присваивания значения.
\item \textbf{Арифметические операторы} --- знаки \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\^} для выполнения соответствующих математических операций. \item \textbf{Арифметические операторы} --- знаки \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\^} для выполнения соответствующих математических операций.
\item \textbf{Скобки} --- круглые скобки \texttt{(} и \texttt{)}, используемые для задания порядка вычислений в арифметических выражениях. \item \textbf{Левая скобка} --- символ \texttt{(}, используемый для задания порядка вычислений в арифметических выражениях.
\item \textbf{Правая скобка} --- символ \texttt{)}, используемый для задания порядка вычислений в арифметических выражениях.
\item \textbf{Разделитель выражений} --- символ вертикальной черты \texttt{|}, отделяющий одно арифметическое выражение от другого. \item \textbf{Разделитель выражений} --- символ вертикальной черты \texttt{|}, отделяющий одно арифметическое выражение от другого.
@@ -284,21 +286,21 @@
\begin{enumerate} \begin{enumerate}
\item \textbf{Идентификаторы.} \item \textbf{Идентификаторы.}
\begin{center} \begin{center}
$R_{\text{Identifier}}$ = [A-Za-z\_][A-Za-z\_0-9]\{0,15\}(?![A-Za-z\_0-9]) $R_{\text{Identifier}}$ = [A-Za-z][A-Za-z\_0-9]\{0,15\}(?![A-Za-z\_0-9])
\end{center} \end{center}
Первый символ — латинская буква или подчёркивание. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора). Первый символ — латинская буква. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора).
\item \textbf{Комплексные числа в показательной форме.} \item \textbf{Комплексные числа в показательной форме.}
\begin{center} \begin{center}
$R_{\text{Complex}}$ = [+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?E[+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?(?!E) $R_{\text{Complex}}$ = [+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?E[+-]?\textbackslash{}d+(?:\textbackslash{}.\textbackslash{}d+)?
\end{center} \end{center}
Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число. В конце используется \textit{negative lookahead}, чтобы за числом не мог сразу следовать символ \texttt{E}. Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число.
\item \textbf{Оператор присваивания.} \item \textbf{Оператор присваивания.}
\begin{center} \begin{center}
$R_{\text{Assign}}$ = \textbackslash{}:=(?![\textbackslash{}:=]) $R_{\text{Assign}}$ = \textbackslash{}:=
\end{center} \end{center}
\item \textbf{Арифметические операторы.} \item \textbf{Арифметические операторы.}
@@ -306,9 +308,14 @@
$R_{\text{ArithmeticOp}}$ = [+\textbackslash{}-\textbackslash{}*/\textbackslash{}\^{}] $R_{\text{ArithmeticOp}}$ = [+\textbackslash{}-\textbackslash{}*/\textbackslash{}\^{}]
\end{center} \end{center}
\item \textbf{Скобки.} \item \textbf{Левая скобка.}
\begin{center} \begin{center}
$R_{\text{Paren}}$ = [()] $R_{\text{LeftParen}}$ = \textbackslash{}(
\end{center}
\item \textbf{Правая скобка.}
\begin{center}
$R_{\text{RightParen}}$ = \textbackslash{})
\end{center} \end{center}
\item \textbf{Разделитель выражений.} \item \textbf{Разделитель выражений.}
@@ -324,9 +331,9 @@
В класс ошибок попадают все лексемы, неподошедшие ни под какой другой класс. Некорректная лексема считывается с помощью следующего регулярного выражения: В класс ошибок попадают все лексемы, неподошедшие ни под какой другой класс. Некорректная лексема считывается с помощью следующего регулярного выражения:
\begin{center} \begin{center}
$R_{\text{Error}}$ = \textbackslash{}S+ $R_{\text{Error}}$ = .[\textasciicircum|()+\textbackslash{}-*/\textasciicircum{}:=\textbackslash{}s]*
\end{center} \end{center}
Это регулярное выражение считывает все непробельные символы до первого пробельного символа. Это регулярное выражение считывает как минимум один символ, чтобы анализ текста не завис. Затем регулярное выражение считывает все символы до первого пробела, арифметического оператора, левой или правой скобки, или оператора присваивания.
\subsection{Алгоритм лексического анализа} \subsection{Алгоритм лексического анализа}
Алгоритм лексического анализа состоит из следующих шагов: Алгоритм лексического анализа состоит из следующих шагов:
@@ -407,19 +414,30 @@ class LexemeType:
\end{lstlisting} \end{lstlisting}
\subsection{Класс Lexer} \subsection{Класс Lexer}
Класс Lexer представляет собой лексический анализатор. Код определения класса представлен в листинге~\ref{lst:Lexer}. В классе всего одно поле: Класс Lexer представляет собой лексический анализатор. Код определения класса представлен в листинге~\ref{lst:Lexer}. В классе определены следующие поля:
\texttt{lexeme\_types} -- список объектов класса \texttt{LexemeType}. \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{\_consume\_error}.
Метод \texttt{analyze} выполняет лексический разбор входного текста. Он принимает строку \texttt{text}, содержащую текст программы, и возвращает список объектов типа \texttt{Lexem}. Метод поочерёдно применяет каждый тип лексемы из \texttt{lexeme\_types}, пытаясь извлечь очередную лексему. Если хотя бы один тип лексемы успешно извлекает лексему, она добавляется в результат, а оставшийся текст анализируется далее. Если ни одна лексема не подошла, вызывается метод \texttt{\_consume\_error} для обработки ошибки. Метод \texttt{analyze} выполняет лексический разбор входного текста. Он принимает строку \texttt{text}, содержащую текст программы, и возвращает список объектов типа \texttt{Lexem}. Метод поочерёдно применяет каждый тип лексемы из \texttt{lexeme\_types}, пытаясь извлечь очередную лексему. Если хотя бы один тип лексемы успешно извлекает лексему, она добавляется в результат (если её тип не входит в \texttt{skip\_types}), а оставшийся текст анализируется далее. Если ни одна лексема не подошла, вызывается метод \texttt{\_consume\_error} для обработки ошибки.
Метод \texttt{\_consume\_error} используется для обработки ситуаций, когда входной фрагмент не соответствует ни одному из допустимых шаблонов. Он находит первую непробельную последовательность символов, сообщает об ошибке в консоль и создаёт лексему с типом \texttt{"ERROR"}. Возвращает эту ошибочную лексему и оставшийся текст. Метод \texttt{\_consume\_error} используется для обработки ситуаций, когда входной фрагмент не соответствует ни одному из допустимых шаблонов. Он использует \texttt{error\_regex} для поиска ошибочной последовательности символов, сообщает об ошибке в консоль и создаёт лексему с типом \texttt{"ERROR"}. Возвращает эту ошибочную лексему и оставшийся текст.
\begin{lstlisting}[caption={Определение класса Lexer.}, label={lst:Lexer}] \begin{lstlisting}[caption={Определение класса Lexer.}, label={lst:Lexer}]
class Lexer: class Lexer:
def __init__(self, lexeme_types: Iterable[LexemeType]): def __init__(
self,
lexeme_types: Iterable[LexemeType],
error_regex: str,
skip_types: Iterable[str] = [],
):
self.lexeme_types = lexeme_types 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]: def analyze(self, text: str) -> list[Lexem]:
lexems: list[Lexem] = [] lexems: list[Lexem] = []
@@ -427,16 +445,16 @@ class Lexer:
for lex_type in self.lexeme_types: for lex_type in self.lexeme_types:
lexem, new_text = lex_type.consume(text) lexem, new_text = lex_type.consume(text)
if lexem: if lexem:
lexems.append(lexem) if lexem.type_name not in self.skip_types:
lexems.append(lexem)
text = new_text text = new_text
break break
else: else:
error_lexeme, text = self._consume_error(text) error_lexeme, text = self._consume_error(text)
lexems.append(error_lexeme)
return lexems return lexems
def _consume_error(self, text: str) -> tuple[Lexem, str]: def _consume_error(self, text: str) -> tuple[Lexem, str]:
match = re.match(r"\s*(\S+)", text) match = self.error_regex.match(text)
err_text = match.group(1) if match else text.strip() err_text = match.group(1) if match else text.strip()
print(f"Недопустимая лексема: {err_text}") print(f"Недопустимая лексема: {err_text}")
rest = text[match.end() :] if match else "" rest = text[match.end() :] if match else ""
@@ -482,16 +500,17 @@ def exp_form_to_complex(exp_str: str) -> str:
\begin{lstlisting}[caption={Определение лексем в LEXEME\_TYPES.}, label={lst:LexemeTypes}] \begin{lstlisting}[caption={Определение лексем в LEXEME\_TYPES.}, label={lst:LexemeTypes}]
LEXEME_TYPES: dict[str, LexemeType] = { LEXEME_TYPES: dict[str, LexemeType] = {
"IDENTIFIER": LexemeType( "IDENTIFIER": LexemeType(
"IDENTIFIER", r"[A-Za-z_][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper() "IDENTIFIER", r"[A-Za-z][A-Za-z_0-9]{0,15}(?![A-Za-z_0-9])", IdentifierMapper()
), ),
"COMPLEX": LexemeType( "COMPLEX": LexemeType(
"COMPLEX", "COMPLEX",
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?(?!E)", r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?",
exp_form_to_complex, exp_form_to_complex,
), ),
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"), "ASSIGN": LexemeType("ASSIGN", r"\:="),
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"), "ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
"PAREN": LexemeType("PAREN", r"[()]"), "LPAREN": LexemeType("LPAREN", r"\("),
"RPAREN": LexemeType("RPAREN", r"\)"),
"SEPARATOR": LexemeType("SEPARATOR", r"\|"), "SEPARATOR": LexemeType("SEPARATOR", r"\|"),
"COMMENT": LexemeType("COMMENT", r"\#.*"), "COMMENT": LexemeType("COMMENT", r"\#.*"),
} }
@@ -502,7 +521,7 @@ LEXEME_TYPES: dict[str, LexemeType] = {
\begin{lstlisting}[caption={Функция analyze\_and\_print\_table.}, label={lst:AnalyzePrint}] \begin{lstlisting}[caption={Функция analyze\_and\_print\_table.}, label={lst:AnalyzePrint}]
def analyze_and_print_table(code: str): def analyze_and_print_table(code: str):
lexer = Lexer(LEXEME_TYPES.values()) lexer = Lexer(LEXEME_TYPES.values(), ERROR_REGEX, skip_types=["COMMENT"])
lexemes = lexer.analyze(code) lexemes = lexer.analyze(code)
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"]) table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])
@@ -511,6 +530,8 @@ def analyze_and_print_table(code: str):
print(table) print(table)
print() print()
LEXEME_TYPES["IDENTIFIER"].value_func = IdentifierMapper()
\end{lstlisting} \end{lstlisting}
\subsection{Функция \texttt{main}} \subsection{Функция \texttt{main}}
@@ -576,7 +597,7 @@ x := x + -2.5E+3 |
\begin{figure}[h!] \begin{figure}[h!]
\centering \centering
\includegraphics[width=0.8\linewidth]{img/result3.png} \includegraphics[width=0.7\linewidth]{img/result3.png}
\caption{Результат работы лексического анализатора на программе 3 (листинг~\ref{lst:prog3}).} \caption{Результат работы лексического анализатора на программе 3 (листинг~\ref{lst:prog3}).}
\label{fig:result3} \label{fig:result3}
\end{figure} \end{figure}
@@ -596,7 +617,11 @@ x := x + -2.5E+3 |
\newpage \newpage
\section*{Заключение} \section*{Заключение}
\addcontentsline{toc}{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{PrettyTable}.