Compare commits
16 Commits
lab2
...
9cad2ede63
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cad2ede63 | |||
| 6d4301a5d6 | |||
| 0fb5794fb6 | |||
| fb67acc55e | |||
| 4448c675dd | |||
| 97a78a4ccc | |||
| 5bd140cd39 | |||
| a60d2a9db7 | |||
| 96c9e6db53 | |||
| 681b56b31b | |||
| d7a0e400cd | |||
| 3c09cc0e9b | |||
| 529da8d98e | |||
| c186941dce | |||
| 20782a2473 | |||
| 3963d304ec |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 20 KiB |
@@ -34,8 +34,15 @@ class LexemeType:
|
||||
|
||||
|
||||
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.skip_types = skip_types
|
||||
self.error_regex = re.compile(r"\s*(" + error_regex + ")")
|
||||
|
||||
def analyze(self, text: str) -> list[Lexem]:
|
||||
lexems: list[Lexem] = []
|
||||
@@ -43,16 +50,16 @@ class Lexer:
|
||||
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)
|
||||
lexems.append(error_lexeme)
|
||||
return lexems
|
||||
|
||||
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()
|
||||
print(f"Недопустимая лексема: {err_text}")
|
||||
rest = text[match.end() :] if match else ""
|
||||
|
||||
@@ -2,9 +2,8 @@ import math
|
||||
import os
|
||||
from typing import Callable
|
||||
|
||||
from prettytable import PrettyTable
|
||||
|
||||
from lexer import LexemeType, Lexer
|
||||
from prettytable import PrettyTable
|
||||
|
||||
|
||||
class IdentifierMapper:
|
||||
@@ -35,30 +34,34 @@ def exp_form_to_complex(exp_str: str) -> str:
|
||||
LEXEME_TYPES: dict[str, LexemeType] = {
|
||||
# 1. Идентификаторы
|
||||
"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. Комплексные числа в показательной форме (на самом деле — числа в экспоненциальной форме)
|
||||
"COMPLEX": LexemeType(
|
||||
# "COMPLEX", r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?!E)", exp_form_to_complex
|
||||
"COMPLEX",
|
||||
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?(?!E)",
|
||||
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?",
|
||||
exp_form_to_complex,
|
||||
),
|
||||
# 3. Оператор присваивания :=
|
||||
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"),
|
||||
"ASSIGN": LexemeType("ASSIGN", r"\:="),
|
||||
# 4. Арифметические операторы
|
||||
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
|
||||
# 5. Скобки
|
||||
"PAREN": LexemeType("PAREN", r"[()]"),
|
||||
# 6. Разделитель выражений |
|
||||
# 5. Левая скобка
|
||||
"LPAREN": LexemeType("LPAREN", r"\("),
|
||||
# 6. Правая скобка
|
||||
"RPAREN": LexemeType("RPAREN", r"\)"),
|
||||
# 7. Разделитель выражений |
|
||||
"SEPARATOR": LexemeType("SEPARATOR", r"\|"),
|
||||
# 7. Комментарии от # до конца строки
|
||||
# 8. Комментарии от # до конца строки
|
||||
"COMMENT": LexemeType("COMMENT", r"\#.*"),
|
||||
}
|
||||
|
||||
ERROR_REGEX = r".[^|()+\-*/^:=\s]*"
|
||||
|
||||
|
||||
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)
|
||||
|
||||
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
\large{Лабораторная работа №1}\\
|
||||
\large{<<Создание лексического анализатора>>}\\
|
||||
\large{по дисциплине}\\
|
||||
\large{<<Математическая логика>>}\\
|
||||
\large{<<Математическая логика и теория автоматов>>}\\
|
||||
\large{Вариант 15}\\
|
||||
|
||||
% \hfill \break
|
||||
@@ -224,7 +224,9 @@
|
||||
|
||||
\item \textbf{Арифметические операторы} --- знаки \texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\^} для выполнения соответствующих математических операций.
|
||||
|
||||
\item \textbf{Скобки} --- круглые скобки \texttt{(} и \texttt{)}, используемые для задания порядка вычислений в арифметических выражениях.
|
||||
\item \textbf{Левая скобка} --- символ \texttt{(}, используемый для задания порядка вычислений в арифметических выражениях.
|
||||
|
||||
\item \textbf{Правая скобка} --- символ \texttt{)}, используемый для задания порядка вычислений в арифметических выражениях.
|
||||
|
||||
\item \textbf{Разделитель выражений} --- символ вертикальной черты \texttt{|}, отделяющий одно арифметическое выражение от другого.
|
||||
|
||||
@@ -284,21 +286,21 @@
|
||||
\begin{enumerate}
|
||||
\item \textbf{Идентификаторы.}
|
||||
\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}
|
||||
|
||||
Первый символ — латинская буква или подчёркивание. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора).
|
||||
Первый символ — латинская буква. Далее допускается до 15 символов, включая буквы, цифры и подчёркивания. В конце используется \textit{negative lookahead}, чтобы за идентификатором не следовал символ, допустимый внутри него (иначе это была бы часть более длинного идентификатора).
|
||||
|
||||
\item \textbf{Комплексные числа в показательной форме.}
|
||||
\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}
|
||||
|
||||
Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число. В конце используется \textit{negative lookahead}, чтобы за числом не мог сразу следовать символ \texttt{E}.
|
||||
Опциональный знак в начале числа, целое или десятичное число до \texttt{E}, затем обязательный символ \texttt{E}, снова опциональный знак, и целое или десятичное число.
|
||||
|
||||
\item \textbf{Оператор присваивания.}
|
||||
\begin{center}
|
||||
$R_{\text{Assign}}$ = \textbackslash{}:=(?![\textbackslash{}:=])
|
||||
$R_{\text{Assign}}$ = \textbackslash{}:=
|
||||
\end{center}
|
||||
|
||||
\item \textbf{Арифметические операторы.}
|
||||
@@ -306,9 +308,14 @@
|
||||
$R_{\text{ArithmeticOp}}$ = [+\textbackslash{}-\textbackslash{}*/\textbackslash{}\^{}]
|
||||
\end{center}
|
||||
|
||||
\item \textbf{Скобки.}
|
||||
\item \textbf{Левая скобка.}
|
||||
\begin{center}
|
||||
$R_{\text{Paren}}$ = [()]
|
||||
$R_{\text{LeftParen}}$ = \textbackslash{}(
|
||||
\end{center}
|
||||
|
||||
\item \textbf{Правая скобка.}
|
||||
\begin{center}
|
||||
$R_{\text{RightParen}}$ = \textbackslash{})
|
||||
\end{center}
|
||||
|
||||
\item \textbf{Разделитель выражений.}
|
||||
@@ -324,9 +331,9 @@
|
||||
|
||||
В класс ошибок попадают все лексемы, неподошедшие ни под какой другой класс. Некорректная лексема считывается с помощью следующего регулярного выражения:
|
||||
\begin{center}
|
||||
$R_{\text{Error}}$ = \textbackslash{}S+
|
||||
$R_{\text{Error}}$ = .[\textasciicircum|()+\textbackslash{}-*/\textasciicircum{}:=\textbackslash{}s]*
|
||||
\end{center}
|
||||
Это регулярное выражение считывает все непробельные символы до первого пробельного символа.
|
||||
Это регулярное выражение считывает как минимум один символ, чтобы анализ текста не завис. Затем регулярное выражение считывает все символы до первого пробела, арифметического оператора, левой или правой скобки, или оператора присваивания.
|
||||
|
||||
\subsection{Алгоритм лексического анализа}
|
||||
Алгоритм лексического анализа состоит из следующих шагов:
|
||||
@@ -407,19 +414,30 @@ class LexemeType:
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection{Класс Lexer}
|
||||
Класс Lexer представляет собой лексический анализатор. Код определения класса представлен в листинге~\ref{lst:Lexer}. В классе всего одно поле:
|
||||
\texttt{lexeme\_types} -- список объектов класса \texttt{LexemeType}.
|
||||
Класс 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{\_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}]
|
||||
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.skip_types = skip_types
|
||||
self.error_regex = re.compile(r"\s*(" + error_regex + ")")
|
||||
|
||||
def analyze(self, text: str) -> list[Lexem]:
|
||||
lexems: list[Lexem] = []
|
||||
@@ -427,16 +445,16 @@ class Lexer:
|
||||
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)
|
||||
lexems.append(error_lexeme)
|
||||
return lexems
|
||||
|
||||
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()
|
||||
print(f"Недопустимая лексема: {err_text}")
|
||||
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}]
|
||||
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()
|
||||
"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+)?(?!E)",
|
||||
r"[+-]?\d+(?:\.\d+)?E[+-]?\d+(?:\.\d+)?",
|
||||
exp_form_to_complex,
|
||||
),
|
||||
"ASSIGN": LexemeType("ASSIGN", r"\:=(?![\:=])"),
|
||||
"ASSIGN": LexemeType("ASSIGN", r"\:="),
|
||||
"ARITHMETIC_OP": LexemeType("ARITHMETIC_OP", r"[+\-*/^]"),
|
||||
"PAREN": LexemeType("PAREN", r"[()]"),
|
||||
"LPAREN": LexemeType("LPAREN", r"\("),
|
||||
"RPAREN": LexemeType("RPAREN", r"\)"),
|
||||
"SEPARATOR": LexemeType("SEPARATOR", r"\|"),
|
||||
"COMMENT": LexemeType("COMMENT", r"\#.*"),
|
||||
}
|
||||
@@ -502,7 +521,7 @@ LEXEME_TYPES: dict[str, LexemeType] = {
|
||||
|
||||
\begin{lstlisting}[caption={Функция analyze\_and\_print\_table.}, label={lst:AnalyzePrint}]
|
||||
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)
|
||||
|
||||
table = PrettyTable(["Лексема", "Тип лексемы", "Значение"])
|
||||
@@ -511,6 +530,8 @@ def analyze_and_print_table(code: str):
|
||||
|
||||
print(table)
|
||||
print()
|
||||
|
||||
LEXEME_TYPES["IDENTIFIER"].value_func = IdentifierMapper()
|
||||
\end{lstlisting}
|
||||
|
||||
\subsection{Функция \texttt{main}}
|
||||
@@ -576,7 +597,7 @@ x := x + -2.5E+3 |
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.8\linewidth]{img/result3.png}
|
||||
\includegraphics[width=0.7\linewidth]{img/result3.png}
|
||||
\caption{Результат работы лексического анализатора на программе 3 (листинг~\ref{lst:prog3}).}
|
||||
\label{fig:result3}
|
||||
\end{figure}
|
||||
@@ -596,7 +617,11 @@ x := x + -2.5E+3 |
|
||||
\newpage
|
||||
\section*{Заключение}
|
||||
\addcontentsline{toc}{section}{Заключение}
|
||||
В ходе выполнения лабораторной работы была написана программа, которая выполняет лексический анализ входного текста в соответствии с вариантом лабораторной работы и выводит в консоль таблицу лексем с указанием их типов и значений. Также программа отдельно выводит сообщения об ошибочных лексемах.
|
||||
В ходе выполнения лабораторной работы был разработан лексический анализатор для языка, поддерживающего идентификаторы до 16 символов, комплексные числа в экспоненциальной форме, оператор присваивания (\texttt{:=}), арифметические операторы (\texttt{+}, \texttt{-}, \texttt{*}, \texttt{/}, \texttt{\textasciicircum}), скобки (\texttt{(}, \texttt{)}), разделитель выражений (\texttt{|}), комментарии (\texttt{\#}). Для каждого типа лексем было разработано отдельное регулярное выражение. Предложенная программная реализация лексического анализатора выводит в консоль таблицу лексем с указанием их типов и значений. Также программа отдельно выводит сообщения об ошибочных лексемах.
|
||||
|
||||
Данный язык является автоматным, так как представим в виде синтаксической диаграммы. Автоматные грамматики — самые простые из формальных грамматик. Они являются подмножеством контекстно-свободных грамматик.
|
||||
|
||||
|
||||
|
||||
Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы лексического анализатора вынесена в отдельный класс \texttt{Lexer}. Логика работы с типами лексем в класс \texttt{LexemeType}. Также в качестве достоинства можно отметить удобочитаемый вывод таблиц в консоли с помощью библиотеки \texttt{PrettyTable}.
|
||||
|
||||
|
||||
BIN
lab2/img/ka.png
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 172 KiB |
BIN
lab2/img/nka.png
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
@@ -27,25 +27,28 @@ class FiniteAutomaton:
|
||||
|
||||
for char in input_string:
|
||||
if char not in self.alphabet:
|
||||
return f"Символ '{char}' не из алфавита", transitions_path
|
||||
return f"Символ '{char}' не из алфавита ✗", transitions_path
|
||||
|
||||
next_state = self._get_next_state(current_state, char)
|
||||
|
||||
if next_state is None:
|
||||
return "Строка не соответствует", transitions_path
|
||||
return "Строка не соответствует ✗", transitions_path
|
||||
|
||||
transitions_path.append(next_state)
|
||||
current_state = next_state
|
||||
|
||||
return (
|
||||
"Строка соответствует"
|
||||
"Строка соответствует ✓"
|
||||
if current_state in self.final_states
|
||||
else "Строка не соответствует"
|
||||
else "Строка не соответствует ✗"
|
||||
), transitions_path
|
||||
|
||||
def generate_random_string(self, stop_probability: float = 0.3) -> str:
|
||||
def generate_random_string(
|
||||
self, stop_probability: float = 0.3
|
||||
) -> tuple[str, list[str]]:
|
||||
result = []
|
||||
current_state = self.initial_state
|
||||
path = [current_state]
|
||||
|
||||
while True:
|
||||
if (
|
||||
@@ -59,5 +62,6 @@ class FiniteAutomaton:
|
||||
char = random.choice(transition)
|
||||
result.append(char)
|
||||
current_state = next_state
|
||||
path.append(current_state)
|
||||
|
||||
return "".join(result)
|
||||
return "".join(result), path
|
||||
|
||||
@@ -2,20 +2,22 @@ from finite_automaton import FiniteAutomaton
|
||||
|
||||
|
||||
def main():
|
||||
alphabet = set("+-0123456789.,eE")
|
||||
alphabet = set("+-0123456789.eE")
|
||||
initial_state = "S0"
|
||||
final_states = {"S2", "S4", "S7", "S8", "S9"}
|
||||
final_states = {"S2", "S3", "S5", "S7", "S10"}
|
||||
|
||||
transitions = {
|
||||
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S8")],
|
||||
"S1": [("123456789", "S2"), ("0", "S8")],
|
||||
"S2": [("0123456789", "S2"), (".,", "S3"), ("eE", "S5")],
|
||||
"S3": [("0123456789", "S4")],
|
||||
"S4": [("0123456789", "S4"), ("eE", "S5")],
|
||||
"S5": [("+-", "S6"), ("123456789", "S7"), ("0", "S9")],
|
||||
"S6": [("123456789", "S7"), ("0", "S9")],
|
||||
"S7": [("0123456789", "S7")],
|
||||
"S8": [(".,", "S3")],
|
||||
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S3"), (".", "S6")],
|
||||
"S1": [("123456789", "S2"), ("0", "S3"), (".", "S6")],
|
||||
"S2": [("0123456789", "S2"), (".", "S5"), ("eE", "S8")],
|
||||
"S3": [("0", "S3"), ("123456789", "S4"), (".", "S5"), ("eE", "S8")],
|
||||
"S4": [("0123456789", "S4"), (".", "S5"), ("eE", "S8")],
|
||||
"S5": [("0123456789", "S5"), ("eE", "S8")],
|
||||
"S6": [("0123456789", "S7")],
|
||||
"S7": [("0123456789", "S7"), ("eE", "S8")],
|
||||
"S8": [("+-", "S9"), ("0123456789", "S10")],
|
||||
"S9": [("0123456789", "S10")],
|
||||
"S10": [("0123456789", "S10")],
|
||||
}
|
||||
|
||||
automaton = FiniteAutomaton(
|
||||
@@ -71,8 +73,9 @@ def main():
|
||||
print(f"Ошибка: {e}")
|
||||
continue
|
||||
|
||||
random_string = automaton.generate_random_string(stop_prob)
|
||||
random_string, path = automaton.generate_random_string(stop_prob)
|
||||
print(f"Сгенерированная строка: {random_string}")
|
||||
print("Путь переходов:", " -> ".join(path))
|
||||
|
||||
else:
|
||||
print(f"Неизвестная команда: {cmd}")
|
||||
|
||||
150
lab2/report.tex
@@ -114,7 +114,7 @@
|
||||
\large{Лабораторная работа №2}\\
|
||||
\large{<<Конечный автомат и регулярное выражение для распознавания форматов вещественных чисел>>}\\
|
||||
\large{по дисциплине}\\
|
||||
\large{<<Математическая логика>>}\\
|
||||
\large{<<Математическая логика и теория автоматов>>}\\
|
||||
\large{Вариант 15}\\
|
||||
|
||||
% \hfill \break
|
||||
@@ -152,27 +152,27 @@
|
||||
|
||||
\textit{Вариант 15}. Соответствие вещественного числа разным форматам представления.
|
||||
|
||||
\newpage
|
||||
\section {Математическое описание}
|
||||
\subsection{Форматы представления вещественных чисел}
|
||||
|
||||
В данной лабораторной работе рассматриваются следующие форматы представления вещественных чисел.
|
||||
\begin{itemize}
|
||||
\item Целые числа, например: \texttt{''123''}, \texttt{''-456''}, \texttt{''0''}, в т. ч. \texttt{''+0''}, \texttt{''-0''}.
|
||||
\item Десятичные числа с двумя возможными разделителями (точка или запятая), например: \texttt{''123.456''}, \texttt{''-456,789''}.
|
||||
\item Экспоненциальная форма (буква E может быть как в верхнем, так и в нижнем регистре), например: \texttt{''1.23E4''}, \texttt{''-4,56e-7''}, \texttt{''7e8''}.
|
||||
\item Десятичные числа с разделителем точка, например: \texttt{''123.456''}, \texttt{''-456.789''}, \texttt{''.5''}.
|
||||
\item Экспоненциальная форма (буква E может быть как в верхнем, так и в нижнем регистре), например: \texttt{''1.23E4''}, \texttt{''-4.56e-7''}, \texttt{''7e8''}.
|
||||
\end{itemize}
|
||||
|
||||
Формальное определение синтаксиса предложенного формата вещественных чисел в БНФ нотации:
|
||||
\begin{verbatim}
|
||||
real ::= decimal | exponential
|
||||
decimal ::= integer [separator digit {digit}]
|
||||
integer ::= [sign] (nonzerodigit {digit} | "0")
|
||||
exponential ::= decimal ("e" | "E") integer
|
||||
real ::= [sign] (integer | float | exponentnumber)
|
||||
sign ::= "+" | "-"
|
||||
|
||||
integer ::= nonzerodigit {digit} | "0" {"0"}
|
||||
nonzerodigit ::= "1" | "2" | ... | "9"
|
||||
digit ::= "0" | nonzerodigit
|
||||
separator ::= "." | ","
|
||||
|
||||
float ::= {digit} "." digitpart | digitpart "."
|
||||
digitpart ::= digit {digit}
|
||||
|
||||
exponentnumber ::= (digitpart | float) exponent
|
||||
exponent ::= ("e" | "E") [sign] digitpart
|
||||
\end{verbatim}
|
||||
Где:
|
||||
\begin{itemize}
|
||||
@@ -182,7 +182,8 @@
|
||||
\end{itemize}
|
||||
|
||||
|
||||
|
||||
\newpage
|
||||
\section {Математическое описание}
|
||||
\subsection{Языки и грамматики}
|
||||
Языком над конечным словарем $\Sigma$ называется произвольное множество конечных цепочек над этим словарем.
|
||||
|
||||
@@ -248,22 +249,30 @@
|
||||
|
||||
Для разных форматов представления вещественных чисел были построены следующие регулярные выражения в соответствии с определённой БНФ нотацией:
|
||||
\begin{itemize}
|
||||
\item Десятичные и целые числа: \\
|
||||
\texttt{[+-]?([1-9][0-9]*|0)([.,][0-9]+)?}
|
||||
\item Целые числа: \\
|
||||
\texttt{[+-]?(0+|[1-9][0-9]*)}
|
||||
\item Десятичные числа: \\
|
||||
\texttt{[+-]?([0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.)}
|
||||
\item Экспоненциальная форма: \\
|
||||
\texttt{[+-]?([1-9][0-9]*|0)([.,][0-9]+)?[eE][+-]?([1-9][0-9]*|0)}
|
||||
\texttt{[+-]?([0-9]+|[0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.)[eE][+-]?[0-9]+}
|
||||
\end{itemize}
|
||||
|
||||
Объединяя все форматы в соответствии с нашей БНФ нотацией, получаем следующее регулярное выражение, которое распознает все форматы вещественных чисел:
|
||||
|
||||
\texttt{[+-]?([1-9][0-9]*|0)([.,][0-9]+)?([eE][+-]?([1-9][0-9]*|0))?}
|
||||
|
||||
Объединяя, получаем следующее регулярное выражение, которое распознает все форматы вещественных чисел в соответствии с БНФ нотацией:
|
||||
\begin{verbatim}
|
||||
[+-]?(
|
||||
0+|
|
||||
[1-9][0-9]*|
|
||||
[0-9]*\.[0-9]+|
|
||||
[0-9]+\.|
|
||||
([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)[eE][+-]?[0-9]+
|
||||
)
|
||||
\end{verbatim}
|
||||
Разберём структуру этого выражения:
|
||||
\begin{itemize}
|
||||
\item \texttt{[+-]?} -- необязательный знак числа (плюс или минус)
|
||||
\item \texttt{([1-9][0-9]*|0)} -- целая часть числа, которая может быть либо нулём, либо цифрой от 1 до 9, за которой следует произвольное количество цифр
|
||||
\item \texttt{([.,][0-9]+)?} -- необязательная десятичная часть, состоящая из разделителя (точка или запятая) и как минимум одной цифры
|
||||
\item \texttt{([eE][+-]?([1-9][0-9]*|0))?} -- необязательная экспоненциальная часть, состоящая из буквы E (в любом регистре), необязательного знака и целого числа
|
||||
\item \texttt{0+|[1-9][0-9]*} -- целое число, которое может быть либо последовательностью нулей, либо цифрой от 1 до 9, за которой следует произвольное количество цифр.
|
||||
\item \texttt{[0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.} -- десятичное число, которое может быть представлено произвольным количеством цифр до и как минимум одной цифрой после точки, либо произвольным количеством цифр и одной точкой в конце.
|
||||
\item \texttt{([0-9]+|[0-9]*\textbackslash.[0-9]+|[0-9]+\textbackslash.)[eE][+-]?[0-9]+} -- экспоненциальная форма числа, состоящая из произвольного количества цифр, либо десятичного числа, за которым следует буква E (в любом регистре), необязательный знак и как минимум одна цифра.
|
||||
\end{itemize}
|
||||
|
||||
Таким образом, полученное регулярное выражение распознаёт формат представления вещественных чисел, рассматриваемый в данной работе, и полностью соответствует формальному определению, представленному в БНФ нотации.
|
||||
@@ -335,27 +344,29 @@
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.,} & \textbf{eE} \\
|
||||
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.} & \textbf{eE} \\
|
||||
\hline
|
||||
$S_0$ & $S_1$ & $S_8$ & $S_2$ & -- & -- \\
|
||||
$S_0$ & $S_1$ & $S_3$ & $S_2$ & $S_6$ & -- \\
|
||||
\hline
|
||||
$S_1$ & -- & $S_8$ & $S_2$ & -- & -- \\
|
||||
$S_1$ & -- & $S_3$ & $S_2$ & $S_6$ & -- \\
|
||||
\hline
|
||||
$S_2$ & -- & $S_2$ & $S_2$ & $S_3$ & $S_5$ \\
|
||||
$S_2$ & -- & $S_2$ & $S_2$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_3$ & -- & $S_4$ & $S_4$ & -- & -- \\
|
||||
$S_3$ & -- & $S_3$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_4$ & -- & $S_4$ & $S_4$ & -- & $S_5$ \\
|
||||
$S_4$ & -- & $S_4$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_5$ & $S_6$ & $S_9$ & $S_7$ & -- & -- \\
|
||||
$S_5$ & -- & $S_5$ & $S_5$ & -- & $S_8$ \\
|
||||
\hline
|
||||
$S_6$ & -- & $S_9$ & $S_7$ & -- & -- \\
|
||||
$S_6$ & -- & $S_7$ & $S_7$ & -- & -- \\
|
||||
\hline
|
||||
$S_7$ & -- & -- & $S_7$ & -- & -- \\
|
||||
$S_7$ & -- & $S_7$ & $S_7$ & -- & $S_8$ \\
|
||||
\hline
|
||||
$S_8$ & -- & -- & -- & $S_3$ & -- \\
|
||||
$S_8$ & $S_9$ & $S_{10}$ & $S_{10}$ & -- & -- \\
|
||||
\hline
|
||||
$S_9$ & -- & -- & -- & -- & -- \\
|
||||
$S_9$ & -- & $S_{10}$ & $S_{10}$ & -- & -- \\
|
||||
\hline
|
||||
$S_{10}$ & -- & $S_{10}$ & $S_{10}$ & -- & -- \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\label{tab:nka}
|
||||
@@ -374,7 +385,6 @@
|
||||
\label{fig:ka}
|
||||
\end{figure}
|
||||
|
||||
Матрица переходов для данного автомата представлена в Таблице~\ref{tab:ka}.
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
@@ -382,27 +392,29 @@
|
||||
\footnotesize
|
||||
\begin{tabularx}{\textwidth}{|c|X|X|X|X|X|}
|
||||
\hline
|
||||
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.,} & \textbf{eE} \\
|
||||
\textbf{Состояние\textbackslash Вход} & \textbf{+-} & \textbf{0} & \textbf{1-9} & \textbf{.} & \textbf{eE} \\
|
||||
\hline
|
||||
$S_0$ & $S_1$ & $S_8$ & $S_2$ & $S_E$ & $S_E$ \\
|
||||
$S_0$ & $S_1$ & $S_3$ & $S_2$ & $S_6$ & $S_E$ \\
|
||||
\hline
|
||||
$S_1$ & $S_E$ & $S_8$ & $S_2$ & $S_E$ & $S_E$ \\
|
||||
$S_1$ & $S_E$ & $S_3$ & $S_2$ & $S_6$ & $S_E$ \\
|
||||
\hline
|
||||
$S_2$ & $S_E$ & $S_2$ & $S_2$ & $S_3$ & $S_5$ \\
|
||||
$S_2$ & $S_E$ & $S_2$ & $S_2$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_3$ & $S_E$ & $S_4$ & $S_4$ & $S_E$ & $S_E$ \\
|
||||
$S_3$ & $S_E$ & $S_3$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_4$ & $S_E$ & $S_4$ & $S_4$ & $S_E$ & $S_5$ \\
|
||||
$S_4$ & $S_E$ & $S_4$ & $S_4$ & $S_5$ & $S_8$ \\
|
||||
\hline
|
||||
$S_5$ & $S_6$ & $S_9$ & $S_7$ & $S_E$ & $S_E$ \\
|
||||
$S_5$ & $S_E$ & $S_5$ & $S_5$ & $S_E$ & $S_8$ \\
|
||||
\hline
|
||||
$S_6$ & $S_E$ & $S_9$ & $S_7$ & $S_E$ & $S_E$ \\
|
||||
$S_6$ & $S_E$ & $S_7$ & $S_7$ & $S_E$ & $S_E$ \\
|
||||
\hline
|
||||
$S_7$ & $S_E$ & $S_E$ & $S_7$ & $S_E$ & $S_E$ \\
|
||||
$S_7$ & $S_E$ & $S_7$ & $S_7$ & $S_E$ & $S_8$ \\
|
||||
\hline
|
||||
$S_8$ & $S_E$ & $S_E$ & $S_E$ & $S_3$ & $S_E$ \\
|
||||
$S_8$ & $S_9$ & $S_{10}$ & $S_{10}$ & $S_E$ & $S_E$ \\
|
||||
\hline
|
||||
$S_9$ & $S_E$ & $S_E$ & $S_E$ & $S_E$ & $S_E$ \\
|
||||
$S_9$ & $S_E$ & $S_{10}$ & $S_{10}$ & $S_E$ & $S_E$ \\
|
||||
\hline
|
||||
$S_{10}$ & $S_E$ & $S_{10}$ & $S_{10}$ & $S_E$ & $S_E$ \\
|
||||
\hline
|
||||
\end{tabularx}
|
||||
\label{tab:ka}
|
||||
@@ -410,7 +422,9 @@
|
||||
|
||||
|
||||
\newpage
|
||||
\phantom{text}
|
||||
Матрица переходов для данного автомата представлена в Таблице~\ref{tab:ka}.
|
||||
|
||||
|
||||
\newpage
|
||||
\section{Особенности реализации}
|
||||
\subsection{Общая структура программы}
|
||||
@@ -502,9 +516,12 @@ def process_input(self, input_string: str) -> tuple[str, list[str]]:
|
||||
\end{enumerate}
|
||||
|
||||
\begin{lstlisting}[caption={Код метода \texttt{generate\_random\_string}.}, label={lst:generate_random_string}]
|
||||
def generate_random_string(self, stop_probability: float = 0.3) -> str:
|
||||
def generate_random_string(
|
||||
self, stop_probability: float = 0.3
|
||||
) -> tuple[str, list[str]]:
|
||||
result = []
|
||||
current_state = self.initial_state
|
||||
path = [current_state]
|
||||
|
||||
while True:
|
||||
if (
|
||||
@@ -518,8 +535,9 @@ def generate_random_string(self, stop_probability: float = 0.3) -> str:
|
||||
char = random.choice(transition)
|
||||
result.append(char)
|
||||
current_state = next_state
|
||||
path.append(current_state)
|
||||
|
||||
return "".join(result)
|
||||
return "".join(result), path
|
||||
\end{lstlisting}
|
||||
|
||||
|
||||
@@ -537,20 +555,22 @@ def generate_random_string(self, stop_probability: float = 0.3) -> str:
|
||||
|
||||
\begin{lstlisting}[caption={Функция main.}, label={lst:Main}]
|
||||
def main():
|
||||
alphabet = set("+-0123456789.,eE")
|
||||
alphabet = set("+-0123456789.eE")
|
||||
initial_state = "S0"
|
||||
final_states = {"S2", "S4", "S7", "S8", "S9"}
|
||||
final_states = {"S2", "S3", "S5", "S7", "S10"}
|
||||
|
||||
transitions = {
|
||||
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S8")],
|
||||
"S1": [("123456789", "S2"), ("0", "S8")],
|
||||
"S2": [("0123456789", "S2"), (".,", "S3"), ("eE", "S5")],
|
||||
"S3": [("0123456789", "S4")],
|
||||
"S4": [("0123456789", "S4"), ("eE", "S5")],
|
||||
"S5": [("+-", "S6"), ("123456789", "S7"), ("0", "S9")],
|
||||
"S6": [("123456789", "S7"), ("0", "S9")],
|
||||
"S7": [("0123456789", "S7")],
|
||||
"S8": [(".,", "S3")],
|
||||
"S0": [("+-", "S1"), ("123456789", "S2"), ("0", "S3"), (".", "S6")],
|
||||
"S1": [("123456789", "S2"), ("0", "S3"), (".", "S6")],
|
||||
"S2": [("0123456789", "S2"), (".", "S5"), ("eE", "S8")],
|
||||
"S3": [("0", "S3"), ("123456789", "S4"), (".", "S5"), ("eE", "S8")],
|
||||
"S4": [("0123456789", "S4"), (".", "S5"), ("eE", "S8")],
|
||||
"S5": [("0123456789", "S5"), ("eE", "S8")],
|
||||
"S6": [("0123456789", "S7")],
|
||||
"S7": [("0123456789", "S7"), ("eE", "S8")],
|
||||
"S8": [("+-", "S9"), ("0123456789", "S10")],
|
||||
"S9": [("0123456789", "S10")],
|
||||
"S10": [("0123456789", "S10")],
|
||||
}
|
||||
|
||||
automaton = FiniteAutomaton(
|
||||
@@ -606,8 +626,9 @@ def main():
|
||||
print(f"Ошибка: {e}")
|
||||
continue
|
||||
|
||||
random_string = automaton.generate_random_string(stop_prob)
|
||||
random_string, path = automaton.generate_random_string(stop_prob)
|
||||
print(f"Сгенерированная строка: {random_string}")
|
||||
print("Путь переходов:", " -> ".join(path))
|
||||
|
||||
else:
|
||||
print(f"Неизвестная команда: {cmd}")
|
||||
@@ -624,7 +645,7 @@ def main():
|
||||
\label{fig:result1}
|
||||
\end{figure}
|
||||
|
||||
На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод.
|
||||
\newpage
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
@@ -633,11 +654,14 @@ def main():
|
||||
\label{fig:wrong}
|
||||
\end{figure}
|
||||
|
||||
На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод.
|
||||
|
||||
|
||||
\newpage
|
||||
\section*{Заключение}
|
||||
\addcontentsline{toc}{section}{Заключение}
|
||||
В ходе выполнения лабораторной работы было построено регулярное выражение для распознавания различных форматов вещественных чисел, созданы недетерминированный и детерминированный конечные автоматы-распознаватели. На основе разработанного автомата была реализована программа, которая проверяет соответствие входной строки заданному формату и генерирует случайные корректные строки.
|
||||
В ходе выполнения лабораторной работы было построено регулярное выражение для распознавания различных форматов вещественных чисел. В соответствии с теоремой Клини по заданному регулярному выражению, задающему регулярный
|
||||
язык, был построен недетерминированный конечный автомат-распознаватель. Затем полученный конечный автомат был детерминирован. На основе разработанного автомата была реализована программа, которая проверяет соответствие входной строки заданному формату и генерирует случайные корректные строки.
|
||||
|
||||
Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы с конечными автоматами вынесена в отдельный класс \texttt{FiniteAutomaton} с четко разделенными методами для проверки строк и генерации случайных строк. Создана удобная интерактивная консольная оболочка для взаимодействия с пользователем, позволяющая выполнять различные команды.
|
||||
|
||||
|
||||
8
lab3/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
!programm
|
||||
!programm/*.py
|
||||
!programm/*.txt
|
||||
BIN
lab3/img/result1.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
lab3/img/wrong.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
3
lab3/programm/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
grammar_*
|
||||
analysis_*
|
||||
generation_*
|
||||
617
lab3/programm/analyze-ll-one.py
Normal file
@@ -0,0 +1,617 @@
|
||||
import random
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
class Grammar:
|
||||
EPSILON: str = "epsilon"
|
||||
|
||||
def __init__(self, text: str):
|
||||
self.productions: OrderedDict[str, list[list[str]]] = OrderedDict()
|
||||
self.start_symbol: str = ""
|
||||
self._parse_productions(text)
|
||||
|
||||
self.terminals: set[str] = set()
|
||||
self._find_terminals()
|
||||
|
||||
self.first_sets: dict[str, set[str]] = {}
|
||||
self._calculate_first_sets()
|
||||
|
||||
self.follow_sets: dict[str, set[str]] = {}
|
||||
self._calculate_follow_sets()
|
||||
|
||||
self.lookup_table: dict[str, dict[str, list[str]]] = {}
|
||||
self._fill_lookup_table()
|
||||
|
||||
# Сопостовляем уникальный номер с каждым правилом
|
||||
self.rule_numbers = {}
|
||||
rule_idx = 1
|
||||
for nt, rules in self.productions.items():
|
||||
for rule in rules:
|
||||
self.rule_numbers[(nt, tuple(rule))] = rule_idx
|
||||
rule_idx += 1
|
||||
|
||||
def _parse_productions(self, text: str):
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
non_terminal, rule = line.split("->")
|
||||
if self.start_symbol == "":
|
||||
self.start_symbol = non_terminal.strip()
|
||||
|
||||
non_terminal = non_terminal.strip()
|
||||
rules = [
|
||||
[
|
||||
symbol.strip('"')
|
||||
for symbol in re.findall(r"\".*?\"|\S+", rule.strip())
|
||||
if symbol.strip('"') != self.EPSILON
|
||||
]
|
||||
for rule in rule.split("|")
|
||||
]
|
||||
|
||||
if non_terminal not in self.productions:
|
||||
self.productions[non_terminal] = []
|
||||
|
||||
self.productions[non_terminal].extend(rules)
|
||||
|
||||
def _find_terminals(self):
|
||||
for rules in self.productions.values():
|
||||
for rule in rules:
|
||||
for symbol in rule:
|
||||
if symbol not in self.productions:
|
||||
self.terminals.add(symbol)
|
||||
|
||||
def _calculate_first_sets(self):
|
||||
# Инициализация FIRST для всех символов
|
||||
for non_terminal in self.productions:
|
||||
self.first_sets[non_terminal] = set()
|
||||
|
||||
# Для терминалов FIRST содержит только сам терминал
|
||||
for terminal in self.terminals:
|
||||
self.first_sets[terminal] = {terminal}
|
||||
|
||||
# Вычисление FIRST для нетерминалов
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for non_terminal, rules in self.productions.items():
|
||||
for rule in rules:
|
||||
if not rule: # Пустое правило (эпсилон)
|
||||
if "" not in self.first_sets[non_terminal]:
|
||||
self.first_sets[non_terminal].add("")
|
||||
changed = True
|
||||
else:
|
||||
all_can_derive_epsilon = True
|
||||
for i, symbol in enumerate(rule):
|
||||
# Добавляем все символы из FIRST(symbol) кроме эпсилон
|
||||
first_without_epsilon = self.first_sets[symbol] - {""}
|
||||
old_size = len(self.first_sets[non_terminal])
|
||||
self.first_sets[non_terminal].update(first_without_epsilon)
|
||||
if len(self.first_sets[non_terminal]) > old_size:
|
||||
changed = True
|
||||
|
||||
# Если symbol не может порождать эпсилон, прерываем
|
||||
if "" not in self.first_sets[symbol]:
|
||||
all_can_derive_epsilon = False
|
||||
break
|
||||
|
||||
# Если все символы в правиле могут порождать эпсилон,
|
||||
# то и нетерминал может порождать эпсилон
|
||||
if (
|
||||
all_can_derive_epsilon
|
||||
and "" not in self.first_sets[non_terminal]
|
||||
):
|
||||
self.first_sets[non_terminal].add("")
|
||||
changed = True
|
||||
|
||||
def _calculate_follow_sets(self):
|
||||
# инициализировать Fo(S) = { $ }, а все остальные Fo(Ai) пустыми множествами
|
||||
for non_terminal in self.productions:
|
||||
self.follow_sets[non_terminal] = set()
|
||||
|
||||
# Добавляем символ конца строки $ для начального символа
|
||||
self.follow_sets[self.start_symbol].add("$")
|
||||
|
||||
# Повторяем, пока в наборах Follow происходят изменения
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
|
||||
# Для каждого нетерминала Aj в грамматике
|
||||
for non_terminal_j, rules in self.productions.items():
|
||||
|
||||
# Для каждого правила Aj → w
|
||||
for rule in rules:
|
||||
|
||||
# Для каждого символа в правиле
|
||||
for i, symbol in enumerate(rule):
|
||||
|
||||
# Если символ - нетерминал Ai
|
||||
if symbol in self.productions:
|
||||
# w' - остаток правила после Ai
|
||||
remainder = rule[i + 1 :] if i + 1 < len(rule) else []
|
||||
|
||||
# Если есть терминалы после Ai (w' не пусто)
|
||||
if remainder:
|
||||
# Вычисляем First(w')
|
||||
first_of_remainder = self._first_of_sequence(remainder)
|
||||
|
||||
# Если терминал a находится в First(w'), то добавляем a к Follow(Ai)
|
||||
for terminal in first_of_remainder - {""}:
|
||||
if terminal not in self.follow_sets[symbol]:
|
||||
self.follow_sets[symbol].add(terminal)
|
||||
changed = True
|
||||
|
||||
# Если ε находится в First(w'), то добавляем Follow(Aj) к Follow(Ai)
|
||||
if "" in first_of_remainder:
|
||||
old_size = len(self.follow_sets[symbol])
|
||||
self.follow_sets[symbol].update(
|
||||
self.follow_sets[non_terminal_j]
|
||||
)
|
||||
if len(self.follow_sets[symbol]) > old_size:
|
||||
changed = True
|
||||
|
||||
# Если w' пусто (Ai в конце правила), то добавляем Follow(Aj) к Follow(Ai)
|
||||
else:
|
||||
old_size = len(self.follow_sets[symbol])
|
||||
self.follow_sets[symbol].update(
|
||||
self.follow_sets[non_terminal_j]
|
||||
)
|
||||
if len(self.follow_sets[symbol]) > old_size:
|
||||
changed = True
|
||||
|
||||
def _first_of_sequence(self, sequence):
|
||||
"""Вычисляет множество FIRST для последовательности символов"""
|
||||
if not sequence:
|
||||
return {""}
|
||||
|
||||
result = set()
|
||||
all_can_derive_epsilon = True
|
||||
|
||||
for symbol in sequence:
|
||||
# Добавляем First(symbol) без эпсилон к результату
|
||||
result.update(self.first_sets[symbol] - {""})
|
||||
|
||||
# Если symbol не может порождать эпсилон, останавливаемся
|
||||
if "" not in self.first_sets[symbol]:
|
||||
all_can_derive_epsilon = False
|
||||
break
|
||||
|
||||
# Если все символы могут порождать эпсилон, добавляем эпсилон к результату
|
||||
if all_can_derive_epsilon:
|
||||
result.add("")
|
||||
|
||||
return result
|
||||
|
||||
def _fill_lookup_table(self):
|
||||
# Для каждого нетерминала A
|
||||
for non_terminal, rules in self.productions.items():
|
||||
# Формируем таблицу синтаксического анализа
|
||||
self.lookup_table[non_terminal] = {}
|
||||
|
||||
# Для каждого правила A → w
|
||||
for rule in rules:
|
||||
# Вычисляем First(w)
|
||||
first_of_rule = self._first_of_sequence(rule)
|
||||
|
||||
# Для каждого терминала a в First(w)
|
||||
for terminal in first_of_rule - {""}:
|
||||
self._add_to_lookup_table(non_terminal, terminal, rule)
|
||||
|
||||
# Если эпсилон в First(w), то для каждого b в Follow(A)
|
||||
if "" in first_of_rule:
|
||||
for terminal in self.follow_sets[non_terminal]:
|
||||
self._add_to_lookup_table(non_terminal, terminal, rule)
|
||||
|
||||
def _add_to_lookup_table(self, non_terminal, terminal, rule):
|
||||
if terminal in self.lookup_table[non_terminal]:
|
||||
raise ValueError(
|
||||
"\nГрамматика не является LL(1)-грамматикой.\n"
|
||||
f'Распознаваемый нетерминал: "{non_terminal}"\n'
|
||||
f'Поступающий на вход терминал: "{terminal}"\n'
|
||||
f"Неоднозначность между правилами:\n"
|
||||
f"{non_terminal} -> {' '.join(rule)}\n"
|
||||
f"{non_terminal} -> {' '.join(self.lookup_table[non_terminal][terminal])}"
|
||||
)
|
||||
self.lookup_table[non_terminal][terminal] = rule
|
||||
|
||||
def format_rules(self) -> str:
|
||||
result = []
|
||||
sorted_rules = sorted(self.rule_numbers.items(), key=lambda x: x[1])
|
||||
|
||||
for rule, number in sorted_rules:
|
||||
non_terminal, symbols = rule
|
||||
rule_text = f"{number}: {non_terminal} -> {' '.join(symbols)}"
|
||||
result.append(rule_text)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def format_lookup_table(self) -> str:
|
||||
"""Форматирует таблицу синтаксического анализа в текстовом виде."""
|
||||
terminals = sorted(list(self.terminals))
|
||||
all_terminals = terminals + ["$"]
|
||||
|
||||
# Определяем ширину каждого столбца
|
||||
header_widths = {"": len("") + 2} # +2 для отступов
|
||||
for term in all_terminals:
|
||||
header_widths[term] = len(term) + 2
|
||||
|
||||
# Определяем ширину столбцов для содержимого таблицы
|
||||
cell_widths = header_widths.copy()
|
||||
for non_terminal in self.productions:
|
||||
nt_width = len(non_terminal) + 2
|
||||
cell_widths[""] = max(cell_widths[""], nt_width)
|
||||
|
||||
for terminal in all_terminals:
|
||||
if terminal in self.lookup_table[non_terminal]:
|
||||
rule = self.lookup_table[non_terminal][terminal]
|
||||
rule_num = self.rule_numbers.get((non_terminal, tuple(rule)), "")
|
||||
cell_content = f"{rule_num}: {' '.join(rule)}"
|
||||
cell_widths[terminal] = max(
|
||||
cell_widths[terminal], len(cell_content) + 2
|
||||
)
|
||||
|
||||
# Создаем верхнюю границу таблицы
|
||||
border = "+"
|
||||
for term in [""] + all_terminals:
|
||||
border += "-" * cell_widths[term] + "+"
|
||||
|
||||
# Создаем заголовок
|
||||
header = "|" + " " * cell_widths[""] + "|"
|
||||
for term in all_terminals:
|
||||
header += f" {term.center(cell_widths[term]-2)} |"
|
||||
|
||||
# Создаем разделительную линию
|
||||
separator = "+"
|
||||
for term in [""] + all_terminals:
|
||||
separator += "=" * cell_widths[term] + "+"
|
||||
|
||||
# Формируем строки таблицы
|
||||
rows = []
|
||||
for non_terminal in sorted(self.productions.keys()):
|
||||
row = f"| {non_terminal.ljust(cell_widths['']-2)} |"
|
||||
for terminal in all_terminals:
|
||||
if terminal in self.lookup_table[non_terminal]:
|
||||
rule = self.lookup_table[non_terminal][terminal]
|
||||
rule_num = self.rule_numbers.get((non_terminal, tuple(rule)), "")
|
||||
cell_content = f"{rule_num}: {' '.join(rule)}"
|
||||
row += f" {cell_content.ljust(cell_widths[terminal]-2)} |"
|
||||
else:
|
||||
row += f" {' - '.ljust(cell_widths[terminal]-2)} |"
|
||||
rows.append(row)
|
||||
|
||||
# Собираем таблицу
|
||||
table = [border, header, separator]
|
||||
for row in rows:
|
||||
table.append(row)
|
||||
table.append(border)
|
||||
|
||||
return "\n".join(table)
|
||||
|
||||
def format_first_sets(self) -> str:
|
||||
"""Форматирует множества FIRST в читаемый вид."""
|
||||
result = []
|
||||
result.append("Множества FIRST:")
|
||||
result.append("=" * 40)
|
||||
|
||||
# Сортируем для гарантии порядка вывода
|
||||
for symbol in sorted(self.first_sets.keys()):
|
||||
# Заменяем пустую строку на эпсилон для лучшей читаемости
|
||||
first_set = {
|
||||
self.EPSILON if item == "" else item for item in self.first_sets[symbol]
|
||||
}
|
||||
result.append(f"FIRST({symbol}) = {{{', '.join(sorted(first_set))}}}")
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def format_follow_sets(self) -> str:
|
||||
"""Форматирует множества FOLLOW в читаемый вид."""
|
||||
result = []
|
||||
result.append("Множества FOLLOW:")
|
||||
result.append("=" * 40)
|
||||
|
||||
# Обрабатываем только нетерминалы
|
||||
for non_terminal in sorted(self.productions.keys()):
|
||||
follow_set = self.follow_sets.get(non_terminal, set())
|
||||
result.append(
|
||||
f"FOLLOW({non_terminal}) = {{{', '.join(sorted(follow_set))}}}"
|
||||
)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def analyze(self, input_tokens: list[str]) -> list[int]:
|
||||
input_tokens = input_tokens.copy()
|
||||
input_tokens += ["$"]
|
||||
input_pos = 0
|
||||
|
||||
# Инициализируем стек с терминальным символом и начальным символом
|
||||
stack = ["$", self.start_symbol]
|
||||
|
||||
rules_applied = []
|
||||
|
||||
while stack:
|
||||
top = stack[-1]
|
||||
|
||||
current_symbol = (
|
||||
input_tokens[input_pos] if input_pos < len(input_tokens) else "$"
|
||||
)
|
||||
|
||||
# Случай 1: Верхний символ стека - нетерминал
|
||||
if top in self.productions:
|
||||
# Ищем правило в таблице синтаксического анализа
|
||||
if current_symbol in self.lookup_table[top]:
|
||||
# Получаем правило
|
||||
production = self.lookup_table[top][current_symbol]
|
||||
|
||||
# Удаляем нетерминал из стека
|
||||
stack.pop()
|
||||
|
||||
# Добавляем правило в rules_applied
|
||||
rule_number = self.rule_numbers[(top, tuple(production))]
|
||||
rules_applied.append(rule_number)
|
||||
|
||||
# Добавляем правило в стек в обратном порядке
|
||||
for symbol in reversed(production):
|
||||
stack.append(symbol)
|
||||
else:
|
||||
expected_symbols = list(self.lookup_table[top].keys())
|
||||
raise ValueError(
|
||||
f"Syntax error: expected one of {expected_symbols}, got '{current_symbol}'"
|
||||
)
|
||||
|
||||
# Случай 2: Верхний символ стека - терминал
|
||||
elif top != "$":
|
||||
if top == current_symbol:
|
||||
# Удаляем терминал из стека
|
||||
stack.pop()
|
||||
# Переходим к следующему символу ввода
|
||||
input_pos += 1
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Syntax error: expected '{top}', got '{current_symbol}'"
|
||||
)
|
||||
|
||||
# Случай 3: Верхний символ стека - $
|
||||
else: # top == "$"
|
||||
if current_symbol == "$":
|
||||
# Успешный синтаксический анализ
|
||||
stack.pop() # Удаляем $ из стека
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Syntax error: unexpected symbols at end of input: '{current_symbol}'"
|
||||
)
|
||||
|
||||
return rules_applied
|
||||
|
||||
def generate(self, symbol: str | None = None) -> tuple[list[str], list[int]]:
|
||||
"""Генерирует предложение по заданной грамматике. Возвращает список терминалов
|
||||
и список номеров применённых правил."""
|
||||
|
||||
if symbol is None:
|
||||
return self.generate(self.start_symbol)
|
||||
|
||||
# Если символ - терминал, возвращаем его
|
||||
if symbol not in self.productions:
|
||||
return [symbol], []
|
||||
|
||||
# Выбираем случайное правило для нетерминала
|
||||
rules = self.productions[symbol]
|
||||
chosen_rule = random.choice(rules)
|
||||
|
||||
# Получаем номер выбранного правила
|
||||
rule_number = self.rule_numbers[(symbol, tuple(chosen_rule))]
|
||||
|
||||
# Инициализируем результаты
|
||||
terminals = []
|
||||
rule_numbers = [rule_number]
|
||||
|
||||
# Разворачиваем каждый символ в правой части правила
|
||||
for s in chosen_rule:
|
||||
sub_terminals, sub_rules = self.generate(s)
|
||||
terminals.extend(sub_terminals)
|
||||
rule_numbers.extend(sub_rules)
|
||||
|
||||
return terminals, rule_numbers
|
||||
|
||||
def generate_derivation_steps(self, rule_numbers: list[int]) -> list[str]:
|
||||
"""Преобразует список номеров правил в последовательность шагов вывода.
|
||||
Возвращает список строк, представляющих каждый шаг вывода."""
|
||||
|
||||
# Получаем соответствие между номерами правил и самими правилами
|
||||
rule_details = {num: rule for rule, num in self.rule_numbers.items()}
|
||||
|
||||
# Начинаем с начального символа
|
||||
current = self.start_symbol
|
||||
steps = [current]
|
||||
|
||||
# Применяем каждое правило по порядку
|
||||
for rule_num in rule_numbers:
|
||||
if rule_num in rule_details:
|
||||
non_terminal, replacement = rule_details[rule_num]
|
||||
|
||||
# Находим первое вхождение нетерминала и заменяем его
|
||||
words = current.split()
|
||||
for i, word in enumerate(words):
|
||||
if word == non_terminal:
|
||||
words[i : i + 1] = replacement
|
||||
break
|
||||
|
||||
current = " ".join(words)
|
||||
steps.append(current)
|
||||
|
||||
return steps
|
||||
|
||||
|
||||
def load_grammar(filename: str = "grammar.txt") -> Grammar | None:
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
text = file.read()
|
||||
grammar = Grammar(text)
|
||||
|
||||
# Сохраняем информацию о грамматике в файлы
|
||||
with open("grammar_rules.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_rules())
|
||||
print("Правила грамматики с номерами сохранены в grammar_rules.txt")
|
||||
|
||||
with open("grammar_lookup_table.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_lookup_table())
|
||||
print(
|
||||
"Таблица синтаксического анализа сохранена в grammar_lookup_table.txt"
|
||||
)
|
||||
|
||||
with open("grammar_first.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_first_sets())
|
||||
print("Множества FIRST сохранены в grammar_first.txt")
|
||||
|
||||
with open("grammar_follow.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_follow_sets())
|
||||
print("Множества FOLLOW сохранены в grammar_follow.txt")
|
||||
|
||||
print(f"Грамматика успешно загружена из файла {filename}")
|
||||
return grammar
|
||||
except FileNotFoundError:
|
||||
print(f"Ошибка: Файл {filename} не найден")
|
||||
return None
|
||||
except ValueError as e:
|
||||
print(f"Ошибка при загрузке грамматики: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Неизвестная ошибка: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def tokenize_string(input_string: str) -> list[str]:
|
||||
input_string = input_string.replace(",", " , ").replace(".", " . ")
|
||||
return input_string.split()
|
||||
|
||||
|
||||
def check_string(grammar: Grammar | None, input_string: str) -> None:
|
||||
if not grammar:
|
||||
print("Ошибка: Грамматика не загружена")
|
||||
return
|
||||
|
||||
print(f"Проверка строки: '{input_string}'")
|
||||
try:
|
||||
input_tokens = tokenize_string(input_string)
|
||||
|
||||
if not input_tokens:
|
||||
parse_result = grammar.analyze(input_tokens)
|
||||
else:
|
||||
try:
|
||||
input_tokens[0] = input_tokens[0][0].lower() + input_tokens[0][1:]
|
||||
parse_result = grammar.analyze(input_tokens)
|
||||
except ValueError as e:
|
||||
input_tokens[0] = input_tokens[0][0].upper() + input_tokens[0][1:]
|
||||
parse_result = grammar.analyze(input_tokens)
|
||||
|
||||
print(f"Результат: Строка соответствует грамматике")
|
||||
print(f"Применённые правила: {parse_result}")
|
||||
|
||||
# Сохраняем результат анализа в файл
|
||||
with open("analysis_result.txt", "w", encoding="utf-8") as f:
|
||||
f.write(f"Input: {input_string}\n")
|
||||
f.write("Applied rules: ")
|
||||
f.write(str(parse_result))
|
||||
f.write("\n\n")
|
||||
f.write("Derivation steps:\n")
|
||||
|
||||
derivation_steps = grammar.generate_derivation_steps(parse_result)
|
||||
for step in derivation_steps:
|
||||
f.write(f"{step}\n")
|
||||
|
||||
print("Подробный результат анализа сохранен в analysis_result.txt")
|
||||
except ValueError as e:
|
||||
print(f"Результат: Строка не соответствует грамматике")
|
||||
print(f"Ошибка: {e}")
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка при анализе: {e}")
|
||||
|
||||
|
||||
def post_process_string(string: str) -> str:
|
||||
if string:
|
||||
string = string[0].upper() + string[1:]
|
||||
string = string.replace(" ,", ",")
|
||||
string = string.replace(" .", ".")
|
||||
string = string.replace(",.", ".")
|
||||
return string
|
||||
|
||||
|
||||
def generate_string(grammar: Grammar | None) -> None:
|
||||
if not grammar:
|
||||
print("Ошибка: Грамматика не загружена")
|
||||
return
|
||||
|
||||
try:
|
||||
terminals, rules = grammar.generate()
|
||||
generated_string = " ".join(terminals)
|
||||
generated_string = post_process_string(generated_string)
|
||||
print(f"Сгенерированная строка: {generated_string}")
|
||||
print(f"Применённые правила: {rules}")
|
||||
|
||||
# Сохраняем результат генерации в файл
|
||||
with open("generation_result.txt", "w", encoding="utf-8") as f:
|
||||
f.write(f"Generated string: {generated_string}\n")
|
||||
f.write("Applied rules: ")
|
||||
f.write(str(rules))
|
||||
f.write("\n\n")
|
||||
f.write("Derivation steps:\n")
|
||||
|
||||
derivation_steps = grammar.generate_derivation_steps(rules)
|
||||
for step in derivation_steps:
|
||||
f.write(f"{step}\n")
|
||||
|
||||
print("Подробный результат генерации сохранен в generation_result.txt")
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка при генерации: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
print("Программа для работы с LL(1)-грамматиками")
|
||||
print("=" * 60)
|
||||
print("Варианты команд:")
|
||||
print(" - load <файл> - загрузить грамматику из файла (по умолчанию grammar.txt)")
|
||||
print(" - check <строка> - проверить, соответствует ли строка грамматике")
|
||||
print(" - generate - сгенерировать случайную строку по грамматике")
|
||||
print(" - exit - выход из программы")
|
||||
print("=" * 60)
|
||||
|
||||
# Загружаем грамматику по умолчанию при старте
|
||||
grammar = load_grammar()
|
||||
|
||||
while True:
|
||||
command = input("\nВведите команду: ").strip()
|
||||
|
||||
if not command:
|
||||
continue
|
||||
|
||||
parts = command.split(maxsplit=1)
|
||||
cmd = parts[0].lower()
|
||||
|
||||
if cmd == "exit":
|
||||
print("Выход из программы.")
|
||||
break
|
||||
|
||||
elif cmd == "load":
|
||||
filename = "grammar.txt"
|
||||
if len(parts) > 1:
|
||||
filename = parts[1].strip()
|
||||
grammar = load_grammar(filename)
|
||||
|
||||
elif cmd == "check":
|
||||
input_string = ""
|
||||
if len(parts) > 1:
|
||||
input_string = parts[1].strip()
|
||||
check_string(grammar, input_string)
|
||||
|
||||
elif cmd == "generate":
|
||||
generate_string(grammar)
|
||||
|
||||
else:
|
||||
print(f"Неизвестная команда: {cmd}")
|
||||
print("Доступные команды: load, check, generate, exit")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
401
lab3/programm/grammar.py
Normal file
@@ -0,0 +1,401 @@
|
||||
import random
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
|
||||
from prettytable import PrettyTable
|
||||
|
||||
|
||||
class Grammar:
|
||||
EPSILON: str = "epsilon"
|
||||
|
||||
def __init__(self, text: str):
|
||||
self.productions: OrderedDict[str, list[list[str]]] = OrderedDict()
|
||||
self.start_symbol: str = ""
|
||||
self._parse_productions(text)
|
||||
|
||||
self.terminals: set[str] = set()
|
||||
self._find_terminals()
|
||||
|
||||
self.first_sets: dict[str, set[str]] = {}
|
||||
self._calculate_first_sets()
|
||||
|
||||
self.follow_sets: dict[str, set[str]] = {}
|
||||
self._calculate_follow_sets()
|
||||
|
||||
self.lookup_table: dict[str, dict[str, list[str]]] = {}
|
||||
self._fill_lookup_table()
|
||||
|
||||
# Сопостовляем уникальный номер с каждым правилом
|
||||
self.rule_numbers = {}
|
||||
rule_idx = 1
|
||||
for nt, rules in self.productions.items():
|
||||
for rule in rules:
|
||||
self.rule_numbers[(nt, tuple(rule))] = rule_idx
|
||||
rule_idx += 1
|
||||
|
||||
def _parse_productions(self, text: str):
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
non_terminal, rule = line.split("->")
|
||||
if self.start_symbol == "":
|
||||
self.start_symbol = non_terminal.strip()
|
||||
|
||||
non_terminal = non_terminal.strip()
|
||||
rules = [
|
||||
[
|
||||
symbol.strip('"')
|
||||
for symbol in re.findall(r"\".*?\"|\S+", rule.strip())
|
||||
if symbol.strip('"') != self.EPSILON
|
||||
]
|
||||
for rule in rule.split("|")
|
||||
]
|
||||
|
||||
if non_terminal not in self.productions:
|
||||
self.productions[non_terminal] = []
|
||||
|
||||
self.productions[non_terminal].extend(rules)
|
||||
|
||||
def _find_terminals(self):
|
||||
for rules in self.productions.values():
|
||||
for rule in rules:
|
||||
for symbol in rule:
|
||||
if symbol not in self.productions:
|
||||
self.terminals.add(symbol)
|
||||
|
||||
def _calculate_first_sets(self):
|
||||
# Инициализация FIRST для всех символов
|
||||
for non_terminal in self.productions:
|
||||
self.first_sets[non_terminal] = set()
|
||||
|
||||
# Для терминалов FIRST содержит только сам терминал
|
||||
for terminal in self.terminals:
|
||||
self.first_sets[terminal] = {terminal}
|
||||
|
||||
# Вычисление FIRST для нетерминалов
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for non_terminal, rules in self.productions.items():
|
||||
for rule in rules:
|
||||
if not rule: # Пустое правило (эпсилон)
|
||||
if "" not in self.first_sets[non_terminal]:
|
||||
self.first_sets[non_terminal].add("")
|
||||
changed = True
|
||||
else:
|
||||
all_can_derive_epsilon = True
|
||||
for i, symbol in enumerate(rule):
|
||||
# Добавляем все символы из FIRST(symbol) кроме эпсилон
|
||||
first_without_epsilon = self.first_sets[symbol] - {""}
|
||||
old_size = len(self.first_sets[non_terminal])
|
||||
self.first_sets[non_terminal].update(first_without_epsilon)
|
||||
if len(self.first_sets[non_terminal]) > old_size:
|
||||
changed = True
|
||||
|
||||
# Если symbol не может порождать эпсилон, прерываем
|
||||
if "" not in self.first_sets[symbol]:
|
||||
all_can_derive_epsilon = False
|
||||
break
|
||||
|
||||
# Если все символы в правиле могут порождать эпсилон,
|
||||
# то и нетерминал может порождать эпсилон
|
||||
if (
|
||||
all_can_derive_epsilon
|
||||
and "" not in self.first_sets[non_terminal]
|
||||
):
|
||||
self.first_sets[non_terminal].add("")
|
||||
changed = True
|
||||
|
||||
def _calculate_follow_sets(self):
|
||||
# инициализировать Fo(S) = { $ }, а все остальные Fo(Ai) пустыми множествами
|
||||
for non_terminal in self.productions:
|
||||
self.follow_sets[non_terminal] = set()
|
||||
|
||||
# Добавляем символ конца строки $ для начального символа
|
||||
self.follow_sets[self.start_symbol].add("$")
|
||||
|
||||
# Повторяем, пока в наборах Follow происходят изменения
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
|
||||
# Для каждого нетерминала Aj в грамматике
|
||||
for non_terminal_j, rules in self.productions.items():
|
||||
|
||||
# Для каждого правила Aj → w
|
||||
for rule in rules:
|
||||
|
||||
# Для каждого символа в правиле
|
||||
for i, symbol in enumerate(rule):
|
||||
|
||||
# Если символ - нетерминал Ai
|
||||
if symbol in self.productions:
|
||||
# w' - остаток правила после Ai
|
||||
remainder = rule[i + 1 :] if i + 1 < len(rule) else []
|
||||
|
||||
# Если есть терминалы после Ai (w' не пусто)
|
||||
if remainder:
|
||||
# Вычисляем First(w')
|
||||
first_of_remainder = self._first_of_sequence(remainder)
|
||||
|
||||
# Если терминал a находится в First(w'), то добавляем a к Follow(Ai)
|
||||
for terminal in first_of_remainder - {""}:
|
||||
if terminal not in self.follow_sets[symbol]:
|
||||
self.follow_sets[symbol].add(terminal)
|
||||
changed = True
|
||||
|
||||
# Если ε находится в First(w'), то добавляем Follow(Aj) к Follow(Ai)
|
||||
if "" in first_of_remainder:
|
||||
old_size = len(self.follow_sets[symbol])
|
||||
self.follow_sets[symbol].update(
|
||||
self.follow_sets[non_terminal_j]
|
||||
)
|
||||
if len(self.follow_sets[symbol]) > old_size:
|
||||
changed = True
|
||||
|
||||
# Если w' пусто (Ai в конце правила), то добавляем Follow(Aj) к Follow(Ai)
|
||||
else:
|
||||
old_size = len(self.follow_sets[symbol])
|
||||
self.follow_sets[symbol].update(
|
||||
self.follow_sets[non_terminal_j]
|
||||
)
|
||||
if len(self.follow_sets[symbol]) > old_size:
|
||||
changed = True
|
||||
|
||||
def _first_of_sequence(self, sequence):
|
||||
"""Вычисляет множество FIRST для последовательности символов"""
|
||||
if not sequence:
|
||||
return {""}
|
||||
|
||||
result = set()
|
||||
all_can_derive_epsilon = True
|
||||
|
||||
for symbol in sequence:
|
||||
# Добавляем First(symbol) без эпсилон к результату
|
||||
result.update(self.first_sets[symbol] - {""})
|
||||
|
||||
# Если symbol не может порождать эпсилон, останавливаемся
|
||||
if "" not in self.first_sets[symbol]:
|
||||
all_can_derive_epsilon = False
|
||||
break
|
||||
|
||||
# Если все символы могут порождать эпсилон, добавляем эпсилон к результату
|
||||
if all_can_derive_epsilon:
|
||||
result.add("")
|
||||
|
||||
return result
|
||||
|
||||
def _fill_lookup_table(self):
|
||||
# Для каждого нетерминала A
|
||||
for non_terminal, rules in self.productions.items():
|
||||
# Формируем таблицу синтаксического анализа
|
||||
self.lookup_table[non_terminal] = {}
|
||||
|
||||
# Для каждого правила A → w
|
||||
for rule in rules:
|
||||
# Вычисляем First(w)
|
||||
first_of_rule = self._first_of_sequence(rule)
|
||||
|
||||
# Для каждого терминала a в First(w)
|
||||
for terminal in first_of_rule - {""}:
|
||||
self._add_to_lookup_table(non_terminal, terminal, rule)
|
||||
|
||||
# Если эпсилон в First(w), то для каждого b в Follow(A)
|
||||
if "" in first_of_rule:
|
||||
for terminal in self.follow_sets[non_terminal]:
|
||||
self._add_to_lookup_table(non_terminal, terminal, rule)
|
||||
|
||||
def _add_to_lookup_table(self, non_terminal, terminal, rule):
|
||||
if terminal in self.lookup_table[non_terminal]:
|
||||
raise ValueError(
|
||||
"\nГрамматика не является LL(1)-грамматикой.\n"
|
||||
f'Распознаваемый нетерминал: "{non_terminal}"\n'
|
||||
f'Поступающий на вход терминал: "{terminal}"\n'
|
||||
f"Неоднозначность между правилами:\n"
|
||||
f"{non_terminal} -> {' '.join(rule)}\n"
|
||||
f"{non_terminal} -> {' '.join(self.lookup_table[non_terminal][terminal])}"
|
||||
)
|
||||
self.lookup_table[non_terminal][terminal] = rule
|
||||
|
||||
def format_rules(self) -> str:
|
||||
result = []
|
||||
sorted_rules = sorted(self.rule_numbers.items(), key=lambda x: x[1])
|
||||
|
||||
for rule, number in sorted_rules:
|
||||
non_terminal, symbols = rule
|
||||
rule_text = f"{number}: {non_terminal} -> {' '.join(symbols)}"
|
||||
result.append(rule_text)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def format_lookup_table(self) -> str:
|
||||
table = PrettyTable()
|
||||
terminals = list(self.terminals)
|
||||
table.field_names = [""] + terminals + ["$"]
|
||||
|
||||
for non_terminal in self.productions:
|
||||
row = [non_terminal]
|
||||
for terminal in terminals + ["$"]:
|
||||
if terminal in self.lookup_table[non_terminal]:
|
||||
rule = self.lookup_table[non_terminal][terminal]
|
||||
rule_num = self.rule_numbers.get((non_terminal, tuple(rule)), "")
|
||||
row.append(f"{rule_num}: {' '.join(rule)}")
|
||||
else:
|
||||
row.append(" - ")
|
||||
table.add_row(row)
|
||||
return str(table)
|
||||
|
||||
def format_first_sets(self) -> str:
|
||||
"""Форматирует множества FIRST в читаемый вид."""
|
||||
result = []
|
||||
result.append("Множества FIRST:")
|
||||
result.append("=" * 40)
|
||||
|
||||
# Сортируем для гарантии порядка вывода
|
||||
for symbol in sorted(self.first_sets.keys()):
|
||||
# Заменяем пустую строку на эпсилон для лучшей читаемости
|
||||
first_set = {
|
||||
self.EPSILON if item == "" else item for item in self.first_sets[symbol]
|
||||
}
|
||||
result.append(f"FIRST({symbol}) = {{{', '.join(sorted(first_set))}}}")
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def format_follow_sets(self) -> str:
|
||||
"""Форматирует множества FOLLOW в читаемый вид."""
|
||||
result = []
|
||||
result.append("Множества FOLLOW:")
|
||||
result.append("=" * 40)
|
||||
|
||||
# Обрабатываем только нетерминалы
|
||||
for non_terminal in sorted(self.productions.keys()):
|
||||
follow_set = self.follow_sets.get(non_terminal, set())
|
||||
result.append(
|
||||
f"FOLLOW({non_terminal}) = {{{', '.join(sorted(follow_set))}}}"
|
||||
)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def analyze(self, input_tokens: list[str]) -> list[int]:
|
||||
input_tokens = input_tokens.copy()
|
||||
input_tokens += ["$"]
|
||||
input_pos = 0
|
||||
|
||||
# Инициализируем стек с терминальным символом и начальным символом
|
||||
stack = ["$", self.start_symbol]
|
||||
|
||||
rules_applied = []
|
||||
|
||||
while stack:
|
||||
top = stack[-1]
|
||||
|
||||
current_symbol = (
|
||||
input_tokens[input_pos] if input_pos < len(input_tokens) else "$"
|
||||
)
|
||||
|
||||
# Случай 1: Верхний символ стека - нетерминал
|
||||
if top in self.productions:
|
||||
# Ищем правило в таблице синтаксического анализа
|
||||
if current_symbol in self.lookup_table[top]:
|
||||
# Получаем правило
|
||||
production = self.lookup_table[top][current_symbol]
|
||||
|
||||
# Удаляем нетерминал из стека
|
||||
stack.pop()
|
||||
|
||||
# Добавляем правило в rules_applied
|
||||
rule_number = self.rule_numbers[(top, tuple(production))]
|
||||
rules_applied.append(rule_number)
|
||||
|
||||
# Добавляем правило в стек в обратном порядке
|
||||
for symbol in reversed(production):
|
||||
stack.append(symbol)
|
||||
else:
|
||||
expected_symbols = list(self.lookup_table[top].keys())
|
||||
raise ValueError(
|
||||
f"Syntax error: expected one of {expected_symbols}, got '{current_symbol}'"
|
||||
)
|
||||
|
||||
# Случай 2: Верхний символ стека - терминал
|
||||
elif top != "$":
|
||||
if top == current_symbol:
|
||||
# Удаляем терминал из стека
|
||||
stack.pop()
|
||||
# Переходим к следующему символу ввода
|
||||
input_pos += 1
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Syntax error: expected '{top}', got '{current_symbol}'"
|
||||
)
|
||||
|
||||
# Случай 3: Верхний символ стека - $
|
||||
else: # top == "$"
|
||||
if current_symbol == "$":
|
||||
# Успешный синтаксический анализ
|
||||
stack.pop() # Удаляем $ из стека
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Syntax error: unexpected symbols at end of input: '{current_symbol}'"
|
||||
)
|
||||
|
||||
return rules_applied
|
||||
|
||||
def generate(self, symbol: str | None = None) -> tuple[list[str], list[int]]:
|
||||
"""Генерирует предложение по заданной грамматике. Возвращает список терминалов
|
||||
и список номеров применённых правил."""
|
||||
|
||||
if symbol is None:
|
||||
return self.generate(self.start_symbol)
|
||||
|
||||
# Если символ - терминал, возвращаем его
|
||||
if symbol not in self.productions:
|
||||
return [symbol], []
|
||||
|
||||
# Выбираем случайное правило для нетерминала
|
||||
rules = self.productions[symbol]
|
||||
chosen_rule = random.choice(rules)
|
||||
|
||||
# Получаем номер выбранного правила
|
||||
rule_number = self.rule_numbers[(symbol, tuple(chosen_rule))]
|
||||
|
||||
# Инициализируем результаты
|
||||
terminals = []
|
||||
rule_numbers = [rule_number]
|
||||
|
||||
# Разворачиваем каждый символ в правой части правила
|
||||
for s in chosen_rule:
|
||||
sub_terminals, sub_rules = self.generate(s)
|
||||
terminals.extend(sub_terminals)
|
||||
rule_numbers.extend(sub_rules)
|
||||
|
||||
return terminals, rule_numbers
|
||||
|
||||
def generate_derivation_steps(self, rule_numbers: list[int]) -> list[str]:
|
||||
"""Преобразует список номеров правил в последовательность шагов вывода.
|
||||
Возвращает список строк, представляющих каждый шаг вывода."""
|
||||
|
||||
# Получаем соответствие между номерами правил и самими правилами
|
||||
rule_details = {num: rule for rule, num in self.rule_numbers.items()}
|
||||
|
||||
# Начинаем с начального символа
|
||||
current = self.start_symbol
|
||||
steps = [current]
|
||||
|
||||
# Применяем каждое правило по порядку
|
||||
for rule_num in rule_numbers:
|
||||
if rule_num in rule_details:
|
||||
non_terminal, replacement = rule_details[rule_num]
|
||||
|
||||
# Находим первое вхождение нетерминала и заменяем его
|
||||
words = current.split()
|
||||
for i, word in enumerate(words):
|
||||
if word == non_terminal:
|
||||
words[i : i + 1] = replacement
|
||||
break
|
||||
|
||||
current = " ".join(words)
|
||||
steps.append(current)
|
||||
|
||||
return steps
|
||||
26
lab3/programm/grammar.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
Предложение -> Повествовательное "."
|
||||
Повествовательное -> ПрямойПорядок | Инверсия
|
||||
ПрямойПорядок -> Подлежащее ДополнениеКПодлежащему Глагол ВторостепенныеЧлены Отрицание
|
||||
Инверсия -> Обстоятельство Глагол Подлежащее ВторостепенныеЧлены Отрицание
|
||||
Подлежащее -> ИменнаяГруппа ПридаточноеПредложение | Местоимение
|
||||
ПридаточноеПредложение -> "," Союз Подлежащее Глагол Отрицание "," | epsilon
|
||||
ВторостепенныеЧлены -> ВторостепенныйЧлен ВторостепенныеЧлены | epsilon
|
||||
ВторостепенныйЧлен -> Обстоятельство | Дополнение
|
||||
Обстоятельство -> ОбстоятельствоВремени | ОбстоятельствоМеста | ОбстоятельствоОбразаДействия
|
||||
ИменнаяГруппа -> АртикльЛибоМестоимение Прилагательные Существительное
|
||||
АртикльЛибоМестоимение -> Артикль | ПритяжательноеМестоимение | epsilon
|
||||
Прилагательные -> Прилагательное Прилагательные | epsilon
|
||||
ДополнениеКПодлежащему -> Предлог Существительное | epsilon
|
||||
Дополнение -> АртикльЛибоМестоимение Прилагательные Существительное
|
||||
ОбстоятельствоМеста -> Предлог АртикльЛибоМестоимение Существительное
|
||||
Союз -> "welcher" | "welche" | "welches"
|
||||
Отрицание -> "nicht" | epsilon
|
||||
Местоимение -> "ich" | "du" | "er" | "sie" | "es" | "wir" | "ihr" | "Sie"
|
||||
ПритяжательноеМестоимение -> "mein" | "dein" | "sein" | "unser" | "euer" | "ihre"
|
||||
Артикль -> "der" | "die" | "das" | "ein" | "eine" | "einen" | "einem" | "einer"
|
||||
Прилагательное -> "alt" | "jung" | "groß" | "klein" | "schön" | "freundlich" | "süß" | "ruhig"
|
||||
Существительное -> "Mann" | "Frau" | "Kind" | "Buch" | "Brief" | "Freund" | "Abendessen" | "Suppe"
|
||||
Предлог -> "in" | "auf" | "unter" | "aus" | "mit" | "für" | "zu" | "am"
|
||||
ОбстоятельствоВремени -> "gestern" | "heute" | "morgen" | "damals" | "jetzt" | "früh" | "spät" | "immer"
|
||||
ОбстоятельствоОбразаДействия -> "schnell" | "langsam" | "gut" | "schlecht" | "laut" | "leise" | "gern" | "fleißig"
|
||||
Глагол -> "las" | "schrieb" | "kochte" | "aß" | "ging" | "kam" | "sagte" | "machte" | "liebte"
|
||||
174
lab3/programm/main.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from grammar import Grammar
|
||||
|
||||
|
||||
def load_grammar(filename: str = "grammar.txt") -> Grammar | None:
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
text = file.read()
|
||||
grammar = Grammar(text)
|
||||
|
||||
# Сохраняем информацию о грамматике в файлы
|
||||
with open("grammar_rules.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_rules())
|
||||
print("Правила грамматики с номерами сохранены в grammar_rules.txt")
|
||||
|
||||
with open("grammar_lookup_table.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_lookup_table())
|
||||
print(
|
||||
"Таблица синтаксического анализа сохранена в grammar_lookup_table.txt"
|
||||
)
|
||||
|
||||
with open("grammar_first.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_first_sets())
|
||||
print("Множества FIRST сохранены в grammar_first.txt")
|
||||
|
||||
with open("grammar_follow.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_follow_sets())
|
||||
print("Множества FOLLOW сохранены в grammar_follow.txt")
|
||||
|
||||
print(f"Грамматика успешно загружена из файла {filename}")
|
||||
return grammar
|
||||
except FileNotFoundError:
|
||||
print(f"Ошибка: Файл {filename} не найден")
|
||||
return None
|
||||
except ValueError as e:
|
||||
print(f"Ошибка при загрузке грамматики: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Неизвестная ошибка: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def tokenize_string(input_string: str) -> list[str]:
|
||||
input_string = input_string.replace(",", " , ").replace(".", " . ")
|
||||
return input_string.split()
|
||||
|
||||
|
||||
def check_string(grammar: Grammar | None, input_string: str) -> None:
|
||||
if not grammar:
|
||||
print("Ошибка: Грамматика не загружена")
|
||||
return
|
||||
|
||||
print(f"Проверка строки: '{input_string}'")
|
||||
try:
|
||||
input_tokens = tokenize_string(input_string)
|
||||
|
||||
if not input_tokens:
|
||||
parse_result = grammar.analyze(input_tokens)
|
||||
else:
|
||||
try:
|
||||
input_tokens[0] = input_tokens[0][0].lower() + input_tokens[0][1:]
|
||||
parse_result = grammar.analyze(input_tokens)
|
||||
except ValueError as e:
|
||||
input_tokens[0] = input_tokens[0][0].upper() + input_tokens[0][1:]
|
||||
parse_result = grammar.analyze(input_tokens)
|
||||
|
||||
print(f"Результат: Строка соответствует грамматике")
|
||||
print(f"Применённые правила: {parse_result}")
|
||||
|
||||
# Сохраняем результат анализа в файл
|
||||
with open("analysis_result.txt", "w", encoding="utf-8") as f:
|
||||
f.write(f"Input: {input_string}\n")
|
||||
f.write("Applied rules: ")
|
||||
f.write(str(parse_result))
|
||||
f.write("\n\n")
|
||||
f.write("Derivation steps:\n")
|
||||
|
||||
derivation_steps = grammar.generate_derivation_steps(parse_result)
|
||||
for step in derivation_steps:
|
||||
f.write(f"{step}\n")
|
||||
|
||||
print("Подробный результат анализа сохранен в analysis_result.txt")
|
||||
except ValueError as e:
|
||||
print(f"Результат: Строка не соответствует грамматике")
|
||||
print(f"Ошибка: {e}")
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка при анализе: {e}")
|
||||
|
||||
|
||||
def post_process_string(string: str) -> str:
|
||||
if string:
|
||||
string = string[0].upper() + string[1:]
|
||||
string = string.replace(" ,", ",")
|
||||
string = string.replace(" .", ".")
|
||||
string = string.replace(",.", ".")
|
||||
return string
|
||||
|
||||
|
||||
def generate_string(grammar: Grammar | None) -> None:
|
||||
if not grammar:
|
||||
print("Ошибка: Грамматика не загружена")
|
||||
return
|
||||
|
||||
try:
|
||||
terminals, rules = grammar.generate()
|
||||
generated_string = " ".join(terminals)
|
||||
generated_string = post_process_string(generated_string)
|
||||
print(f"Сгенерированная строка: {generated_string}")
|
||||
print(f"Применённые правила: {rules}")
|
||||
|
||||
# Сохраняем результат генерации в файл
|
||||
with open("generation_result.txt", "w", encoding="utf-8") as f:
|
||||
f.write(f"Generated string: {generated_string}\n")
|
||||
f.write("Applied rules: ")
|
||||
f.write(str(rules))
|
||||
f.write("\n\n")
|
||||
f.write("Derivation steps:\n")
|
||||
|
||||
derivation_steps = grammar.generate_derivation_steps(rules)
|
||||
for step in derivation_steps:
|
||||
f.write(f"{step}\n")
|
||||
|
||||
print("Подробный результат генерации сохранен в generation_result.txt")
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка при генерации: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
print("Программа для работы с LL(1)-грамматиками")
|
||||
print("=" * 60)
|
||||
print("Варианты команд:")
|
||||
print(" - load <файл> - загрузить грамматику из файла (по умолчанию grammar.txt)")
|
||||
print(" - check <строка> - проверить, соответствует ли строка грамматике")
|
||||
print(" - generate - сгенерировать случайную строку по грамматике")
|
||||
print(" - exit - выход из программы")
|
||||
print("=" * 60)
|
||||
|
||||
# Загружаем грамматику по умолчанию при старте
|
||||
grammar = load_grammar()
|
||||
|
||||
while True:
|
||||
command = input("\nВведите команду: ").strip()
|
||||
|
||||
if not command:
|
||||
continue
|
||||
|
||||
parts = command.split(maxsplit=1)
|
||||
cmd = parts[0].lower()
|
||||
|
||||
if cmd == "exit":
|
||||
print("Выход из программы.")
|
||||
break
|
||||
|
||||
elif cmd == "load":
|
||||
filename = "grammar.txt"
|
||||
if len(parts) > 1:
|
||||
filename = parts[1].strip()
|
||||
grammar = load_grammar(filename)
|
||||
|
||||
elif cmd == "check":
|
||||
input_string = ""
|
||||
if len(parts) > 1:
|
||||
input_string = parts[1].strip()
|
||||
check_string(grammar, input_string)
|
||||
|
||||
elif cmd == "generate":
|
||||
generate_string(grammar)
|
||||
|
||||
else:
|
||||
print(f"Неизвестная команда: {cmd}")
|
||||
print("Доступные команды: load, check, generate, exit")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
lab3/programm/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
prettytable==3.16.0
|
||||
wcwidth==0.2.13
|
||||
1097
lab3/report.tex
Normal file
BIN
lab4/Варианты.jpg
Normal file
|
After Width: | Height: | Size: 161 KiB |
8
lab5/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
!report.tex
|
||||
!img
|
||||
!img/**
|
||||
!programm
|
||||
!programm/*.py
|
||||
!programm/*.txt
|
||||
BIN
lab5/img/homsky.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
lab5/img/result1.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
lab5/img/wrong.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
3
lab5/programm/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
grammar_*
|
||||
analysis_*
|
||||
generation_*
|
||||
435
lab5/programm/grammar.py
Normal file
@@ -0,0 +1,435 @@
|
||||
import random
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from typing import Callable
|
||||
|
||||
from prettytable import PrettyTable
|
||||
|
||||
|
||||
class Grammar:
|
||||
EPSILON: str = "epsilon"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
semantic_action: Callable[[int, tuple[str, list[str]]], None] | None = None,
|
||||
):
|
||||
self.productions: OrderedDict[str, list[list[str]]] = OrderedDict()
|
||||
self.start_symbol: str = ""
|
||||
self._parse_productions(text)
|
||||
|
||||
self.terminals: set[str] = set()
|
||||
self._find_terminals()
|
||||
|
||||
self.first_sets: dict[str, set[str]] = {}
|
||||
self._calculate_first_sets()
|
||||
|
||||
self.follow_sets: dict[str, set[str]] = {}
|
||||
self._calculate_follow_sets()
|
||||
|
||||
self.lookup_table: dict[str, dict[str, list[str]]] = {}
|
||||
self._fill_lookup_table()
|
||||
|
||||
# Сопостовляем уникальный номер с каждым правилом
|
||||
self.rule_numbers = {}
|
||||
rule_idx = 1
|
||||
for nt, rules in self.productions.items():
|
||||
for rule in rules:
|
||||
self.rule_numbers[(nt, tuple(rule))] = rule_idx
|
||||
rule_idx += 1
|
||||
|
||||
# Semantic action callback
|
||||
self.semantic_action = semantic_action
|
||||
|
||||
def _parse_productions(self, text: str):
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
|
||||
non_terminal, rule = line.split("->")
|
||||
if self.start_symbol == "":
|
||||
self.start_symbol = non_terminal.strip()
|
||||
|
||||
non_terminal = non_terminal.strip()
|
||||
rules = [
|
||||
[
|
||||
symbol.strip('"')
|
||||
for symbol in re.findall(r"\".*?\"|\S+", rule.strip())
|
||||
if symbol.strip('"') != self.EPSILON
|
||||
]
|
||||
for rule in rule.split("|")
|
||||
]
|
||||
|
||||
if non_terminal not in self.productions:
|
||||
self.productions[non_terminal] = []
|
||||
|
||||
self.productions[non_terminal].extend(rules)
|
||||
|
||||
def _find_terminals(self):
|
||||
for rules in self.productions.values():
|
||||
for rule in rules:
|
||||
for symbol in rule:
|
||||
if symbol not in self.productions:
|
||||
self.terminals.add(symbol)
|
||||
|
||||
def _calculate_first_sets(self):
|
||||
# Инициализация FIRST для всех символов
|
||||
for non_terminal in self.productions:
|
||||
self.first_sets[non_terminal] = set()
|
||||
|
||||
# Для терминалов FIRST содержит только сам терминал
|
||||
for terminal in self.terminals:
|
||||
self.first_sets[terminal] = {terminal}
|
||||
|
||||
# Вычисление FIRST для нетерминалов
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
for non_terminal, rules in self.productions.items():
|
||||
for rule in rules:
|
||||
if not rule: # Пустое правило (эпсилон)
|
||||
if "" not in self.first_sets[non_terminal]:
|
||||
self.first_sets[non_terminal].add("")
|
||||
changed = True
|
||||
else:
|
||||
all_can_derive_epsilon = True
|
||||
for i, symbol in enumerate(rule):
|
||||
# Добавляем все символы из FIRST(symbol) кроме эпсилон
|
||||
first_without_epsilon = self.first_sets[symbol] - {""}
|
||||
old_size = len(self.first_sets[non_terminal])
|
||||
self.first_sets[non_terminal].update(first_without_epsilon)
|
||||
if len(self.first_sets[non_terminal]) > old_size:
|
||||
changed = True
|
||||
|
||||
# Если symbol не может порождать эпсилон, прерываем
|
||||
if "" not in self.first_sets[symbol]:
|
||||
all_can_derive_epsilon = False
|
||||
break
|
||||
|
||||
# Если все символы в правиле могут порождать эпсилон,
|
||||
# то и нетерминал может порождать эпсилон
|
||||
if (
|
||||
all_can_derive_epsilon
|
||||
and "" not in self.first_sets[non_terminal]
|
||||
):
|
||||
self.first_sets[non_terminal].add("")
|
||||
changed = True
|
||||
|
||||
def _calculate_follow_sets(self):
|
||||
# инициализировать Fo(S) = { $ }, а все остальные Fo(Ai) пустыми множествами
|
||||
for non_terminal in self.productions:
|
||||
self.follow_sets[non_terminal] = set()
|
||||
|
||||
# Добавляем символ конца строки $ для начального символа
|
||||
self.follow_sets[self.start_symbol].add("$")
|
||||
|
||||
# Повторяем, пока в наборах Follow происходят изменения
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
|
||||
# Для каждого нетерминала Aj в грамматике
|
||||
for non_terminal_j, rules in self.productions.items():
|
||||
|
||||
# Для каждого правила Aj → w
|
||||
for rule in rules:
|
||||
|
||||
# Для каждого символа в правиле
|
||||
for i, symbol in enumerate(rule):
|
||||
|
||||
# Если символ - нетерминал Ai
|
||||
if symbol in self.productions:
|
||||
# w' - остаток правила после Ai
|
||||
remainder = rule[i + 1 :] if i + 1 < len(rule) else []
|
||||
|
||||
# Если есть терминалы после Ai (w' не пусто)
|
||||
if remainder:
|
||||
# Вычисляем First(w')
|
||||
first_of_remainder = self._first_of_sequence(remainder)
|
||||
|
||||
# Если терминал a находится в First(w'), то добавляем a к Follow(Ai)
|
||||
for terminal in first_of_remainder - {""}:
|
||||
if terminal not in self.follow_sets[symbol]:
|
||||
self.follow_sets[symbol].add(terminal)
|
||||
changed = True
|
||||
|
||||
# Если ε находится в First(w'), то добавляем Follow(Aj) к Follow(Ai)
|
||||
if "" in first_of_remainder:
|
||||
old_size = len(self.follow_sets[symbol])
|
||||
self.follow_sets[symbol].update(
|
||||
self.follow_sets[non_terminal_j]
|
||||
)
|
||||
if len(self.follow_sets[symbol]) > old_size:
|
||||
changed = True
|
||||
|
||||
# Если w' пусто (Ai в конце правила), то добавляем Follow(Aj) к Follow(Ai)
|
||||
else:
|
||||
old_size = len(self.follow_sets[symbol])
|
||||
self.follow_sets[symbol].update(
|
||||
self.follow_sets[non_terminal_j]
|
||||
)
|
||||
if len(self.follow_sets[symbol]) > old_size:
|
||||
changed = True
|
||||
|
||||
def _first_of_sequence(self, sequence):
|
||||
"""Вычисляет множество FIRST для последовательности символов"""
|
||||
if not sequence:
|
||||
return {""}
|
||||
|
||||
result = set()
|
||||
all_can_derive_epsilon = True
|
||||
|
||||
for symbol in sequence:
|
||||
# Добавляем First(symbol) без эпсилон к результату
|
||||
result.update(self.first_sets[symbol] - {""})
|
||||
|
||||
# Если symbol не может порождать эпсилон, останавливаемся
|
||||
if "" not in self.first_sets[symbol]:
|
||||
all_can_derive_epsilon = False
|
||||
break
|
||||
|
||||
# Если все символы могут порождать эпсилон, добавляем эпсилон к результату
|
||||
if all_can_derive_epsilon:
|
||||
result.add("")
|
||||
|
||||
return result
|
||||
|
||||
def _fill_lookup_table(self):
|
||||
# Для каждого нетерминала A
|
||||
for non_terminal, rules in self.productions.items():
|
||||
# Формируем таблицу синтаксического анализа
|
||||
self.lookup_table[non_terminal] = {}
|
||||
|
||||
# Для каждого правила A → w
|
||||
for rule in rules:
|
||||
# Вычисляем First(w)
|
||||
first_of_rule = self._first_of_sequence(rule)
|
||||
|
||||
# Для каждого терминала a в First(w)
|
||||
for terminal in first_of_rule - {""}:
|
||||
self._add_to_lookup_table(non_terminal, terminal, rule)
|
||||
|
||||
# Если эпсилон в First(w), то для каждого b в Follow(A)
|
||||
if "" in first_of_rule:
|
||||
for terminal in self.follow_sets[non_terminal]:
|
||||
self._add_to_lookup_table(non_terminal, terminal, rule)
|
||||
|
||||
def _add_to_lookup_table(self, non_terminal, terminal, rule):
|
||||
if terminal in self.lookup_table[non_terminal]:
|
||||
raise ValueError(
|
||||
"\nГрамматика не является LL(1)-грамматикой.\n"
|
||||
f'Распознаваемый нетерминал: "{non_terminal}"\n'
|
||||
f'Поступающий на вход терминал: "{terminal}"\n'
|
||||
f"Неоднозначность между правилами:\n"
|
||||
f"{non_terminal} -> {' '.join(rule)}\n"
|
||||
f"{non_terminal} -> {' '.join(self.lookup_table[non_terminal][terminal])}"
|
||||
)
|
||||
self.lookup_table[non_terminal][terminal] = rule
|
||||
|
||||
def format_rules(self) -> str:
|
||||
result = []
|
||||
sorted_rules = sorted(self.rule_numbers.items(), key=lambda x: x[1])
|
||||
|
||||
for rule, number in sorted_rules:
|
||||
non_terminal, symbols = rule
|
||||
rule_text = f"{number}: {non_terminal} -> {' '.join(symbols)}"
|
||||
result.append(rule_text)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def format_lookup_table(self) -> str:
|
||||
table = PrettyTable()
|
||||
terminals = list(self.terminals)
|
||||
table.field_names = [""] + terminals + ["$"]
|
||||
|
||||
for non_terminal in self.productions:
|
||||
row = [non_terminal]
|
||||
for terminal in terminals + ["$"]:
|
||||
if terminal in self.lookup_table[non_terminal]:
|
||||
rule = self.lookup_table[non_terminal][terminal]
|
||||
rule_num = self.rule_numbers.get((non_terminal, tuple(rule)), "")
|
||||
row.append(f"{rule_num}: {' '.join(rule)}")
|
||||
else:
|
||||
row.append(" - ")
|
||||
table.add_row(row)
|
||||
return str(table)
|
||||
|
||||
def format_first_sets(self) -> str:
|
||||
"""Форматирует множества FIRST в читаемый вид."""
|
||||
result = []
|
||||
result.append("Множества FIRST:")
|
||||
result.append("=" * 40)
|
||||
|
||||
# Сортируем для гарантии порядка вывода
|
||||
for symbol in sorted(self.first_sets.keys()):
|
||||
# Заменяем пустую строку на эпсилон для лучшей читаемости
|
||||
first_set = {
|
||||
self.EPSILON if item == "" else item for item in self.first_sets[symbol]
|
||||
}
|
||||
result.append(f"FIRST({symbol}) = {{{', '.join(sorted(first_set))}}}")
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def format_follow_sets(self) -> str:
|
||||
"""Форматирует множества FOLLOW в читаемый вид."""
|
||||
result = []
|
||||
result.append("Множества FOLLOW:")
|
||||
result.append("=" * 40)
|
||||
|
||||
# Обрабатываем только нетерминалы
|
||||
for non_terminal in sorted(self.productions.keys()):
|
||||
follow_set = self.follow_sets.get(non_terminal, set())
|
||||
result.append(
|
||||
f"FOLLOW({non_terminal}) = {{{', '.join(sorted(follow_set))}}}"
|
||||
)
|
||||
|
||||
return "\n".join(result)
|
||||
|
||||
def analyze(self, input_tokens: list[str]) -> list[int]:
|
||||
input_tokens = input_tokens.copy()
|
||||
input_tokens += ["$"]
|
||||
input_pos = 0
|
||||
|
||||
# Инициализируем стек с терминальным символом и начальным символом
|
||||
stack = ["$", self.start_symbol]
|
||||
|
||||
rules_applied = []
|
||||
|
||||
while stack:
|
||||
top = stack[-1]
|
||||
|
||||
current_symbol = (
|
||||
input_tokens[input_pos] if input_pos < len(input_tokens) else "$"
|
||||
)
|
||||
|
||||
# Случай 1: Верхний символ стека - нетерминал
|
||||
if top in self.productions:
|
||||
# Ищем правило в таблице синтаксического анализа
|
||||
if current_symbol in self.lookup_table[top]:
|
||||
# Получаем правило
|
||||
production = self.lookup_table[top][current_symbol]
|
||||
|
||||
# Удаляем нетерминал из стека
|
||||
stack.pop()
|
||||
|
||||
# Добавляем правило в rules_applied
|
||||
rule_number = self.rule_numbers[(top, tuple(production))]
|
||||
rules_applied.append(rule_number)
|
||||
|
||||
# Execute semantic action if provided
|
||||
if self.semantic_action:
|
||||
self.semantic_action(rule_number, (top, production))
|
||||
|
||||
# Добавляем правило в стек в обратном порядке
|
||||
for symbol in reversed(production):
|
||||
stack.append(symbol)
|
||||
else:
|
||||
expected_symbols = list(self.lookup_table[top].keys())
|
||||
raise ValueError(
|
||||
f"Syntax error: expected one of {expected_symbols}, got '{current_symbol}'"
|
||||
)
|
||||
|
||||
# Случай 2: Верхний символ стека - терминал
|
||||
elif top != "$":
|
||||
if top == current_symbol:
|
||||
# Удаляем терминал из стека
|
||||
stack.pop()
|
||||
# Переходим к следующему символу ввода
|
||||
input_pos += 1
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Syntax error: expected '{top}', got '{current_symbol}'"
|
||||
)
|
||||
|
||||
# Случай 3: Верхний символ стека - $
|
||||
else: # top == "$"
|
||||
if current_symbol == "$":
|
||||
# Успешный синтаксический анализ
|
||||
stack.pop() # Удаляем $ из стека
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Syntax error: unexpected symbols at end of input: '{current_symbol}'"
|
||||
)
|
||||
|
||||
return rules_applied
|
||||
|
||||
def generate(self, symbol: str | None = None) -> tuple[list[str], list[int]]:
|
||||
"""Генерирует предложение по заданной грамматике. Возвращает список терминалов
|
||||
и список номеров применённых правил."""
|
||||
|
||||
if symbol is None:
|
||||
return self.generate(self.start_symbol)
|
||||
|
||||
# Если символ - терминал, возвращаем его
|
||||
if symbol not in self.productions:
|
||||
return [symbol], []
|
||||
|
||||
# Выбираем случайное правило для нетерминала
|
||||
rules = self.productions[symbol]
|
||||
chosen_rule = random.choice(rules)
|
||||
|
||||
# Получаем номер выбранного правила
|
||||
rule_number = self.rule_numbers[(symbol, tuple(chosen_rule))]
|
||||
|
||||
# Инициализируем результаты
|
||||
terminals = []
|
||||
rule_numbers = [rule_number]
|
||||
|
||||
# Разворачиваем каждый символ в правой части правила
|
||||
for s in chosen_rule:
|
||||
sub_terminals, sub_rules = self.generate(s)
|
||||
terminals.extend(sub_terminals)
|
||||
rule_numbers.extend(sub_rules)
|
||||
|
||||
return terminals, rule_numbers
|
||||
|
||||
def generate_derivation_steps(self, rule_numbers: list[int]) -> list[str]:
|
||||
"""Преобразует список номеров правил в последовательность шагов вывода.
|
||||
Возвращает список строк, представляющих каждый шаг вывода."""
|
||||
|
||||
# Получаем соответствие между номерами правил и самими правилами
|
||||
rule_details = {num: rule for rule, num in self.rule_numbers.items()}
|
||||
|
||||
# Начинаем с начального символа
|
||||
current = self.start_symbol
|
||||
steps = [current]
|
||||
|
||||
# Применяем каждое правило по порядку
|
||||
for rule_num in rule_numbers:
|
||||
if rule_num in rule_details:
|
||||
non_terminal, replacement = rule_details[rule_num]
|
||||
|
||||
# Находим первое вхождение нетерминала и заменяем его
|
||||
words = current.split()
|
||||
for i, word in enumerate(words):
|
||||
if word == non_terminal:
|
||||
words[i : i + 1] = replacement
|
||||
break
|
||||
|
||||
current = " ".join(words)
|
||||
steps.append(current)
|
||||
|
||||
return steps
|
||||
|
||||
|
||||
class ActionsList:
|
||||
def __init__(self, actions: list[Callable[[int, tuple[str, list[str]]], None]]):
|
||||
self.actions = actions
|
||||
|
||||
def __call__(self, rule_number: int, rule_tuple: tuple[str, list[str]]) -> None:
|
||||
self.actions[rule_number - 1](rule_number, rule_tuple)
|
||||
|
||||
|
||||
class ActionsListWithAppliedCount:
|
||||
def __init__(
|
||||
self, actions: list[Callable[[int, int, tuple[str, list[str]]], None]]
|
||||
):
|
||||
self.actions = actions
|
||||
self.applied_counters = [0] * len(actions)
|
||||
|
||||
def __call__(self, rule_number: int, rule_tuple: tuple[str, list[str]]) -> None:
|
||||
self.applied_counters[rule_number - 1] += 1
|
||||
self.actions[rule_number - 1](
|
||||
rule_number, self.applied_counters[rule_number - 1], rule_tuple
|
||||
)
|
||||
4
lab5/programm/grammar.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
S -> B A b
|
||||
A -> a A B C | b B
|
||||
B -> b
|
||||
C -> c A
|
||||
173
lab5/programm/main.py
Normal file
@@ -0,0 +1,173 @@
|
||||
from grammar import ActionsListWithAppliedCount, Grammar
|
||||
|
||||
|
||||
def load_grammar(filename: str = "grammar.txt") -> Grammar | None:
|
||||
try:
|
||||
# #b - 2 * #a + 3 * #c
|
||||
actions = [
|
||||
lambda rule_number, applied_count, _: print(
|
||||
f"Rule #{rule_number} (applied x{applied_count} times): iconst_1"
|
||||
),
|
||||
lambda rule_number, applied_count, _: print(
|
||||
f"Rule #{rule_number} (applied x{applied_count} times): iconst_2 isub"
|
||||
),
|
||||
lambda rule_number, applied_count, _: print(
|
||||
f"Rule #{rule_number} (applied x{applied_count} times): iconst_1 iadd"
|
||||
),
|
||||
lambda rule_number, applied_count, _: print(
|
||||
f"Rule #{rule_number} (applied x{applied_count} times): iconst_1 iadd"
|
||||
),
|
||||
lambda rule_number, applied_count, _: print(
|
||||
f"Rule #{rule_number} (applied x{applied_count} times): iconst_3 iadd"
|
||||
),
|
||||
]
|
||||
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
text = file.read()
|
||||
grammar = Grammar(text, ActionsListWithAppliedCount(actions))
|
||||
|
||||
# Сохраняем информацию о грамматике в файлы
|
||||
with open("grammar_rules.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_rules())
|
||||
print("Правила грамматики с номерами сохранены в grammar_rules.txt")
|
||||
|
||||
with open("grammar_lookup_table.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_lookup_table())
|
||||
print(
|
||||
"Таблица синтаксического анализа сохранена в grammar_lookup_table.txt"
|
||||
)
|
||||
|
||||
with open("grammar_first.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_first_sets())
|
||||
print("Множества FIRST сохранены в grammar_first.txt")
|
||||
|
||||
with open("grammar_follow.txt", "w", encoding="utf-8") as output_file:
|
||||
output_file.write(grammar.format_follow_sets())
|
||||
print("Множества FOLLOW сохранены в grammar_follow.txt")
|
||||
|
||||
print(f"Грамматика успешно загружена из файла {filename}")
|
||||
return grammar
|
||||
except FileNotFoundError:
|
||||
print(f"Ошибка: Файл {filename} не найден")
|
||||
return None
|
||||
except ValueError as e:
|
||||
print(f"Ошибка при загрузке грамматики: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Неизвестная ошибка: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def tokenize_string(input_string: str) -> list[str]:
|
||||
return input_string.split()
|
||||
|
||||
|
||||
def check_string(grammar: Grammar | None, input_string: str) -> None:
|
||||
if not grammar:
|
||||
print("Ошибка: Грамматика не загружена")
|
||||
return
|
||||
|
||||
print(f"Проверка строки: '{input_string}'")
|
||||
try:
|
||||
input_tokens = tokenize_string(input_string)
|
||||
parse_result = grammar.analyze(input_tokens)
|
||||
|
||||
print(f"Результат: Строка соответствует грамматике")
|
||||
print(f"Применённые правила: {parse_result}")
|
||||
|
||||
# Сохраняем результат анализа в файл
|
||||
with open("analysis_result.txt", "w", encoding="utf-8") as f:
|
||||
f.write(f"Input: {input_string}\n")
|
||||
f.write("Applied rules: ")
|
||||
f.write(str(parse_result))
|
||||
f.write("\n\n")
|
||||
f.write("Derivation steps:\n")
|
||||
|
||||
derivation_steps = grammar.generate_derivation_steps(parse_result)
|
||||
for step in derivation_steps:
|
||||
f.write(f"{step}\n")
|
||||
|
||||
print("Подробный результат анализа сохранен в analysis_result.txt")
|
||||
except ValueError as e:
|
||||
print(f"Результат: Строка не соответствует грамматике")
|
||||
print(f"Ошибка: {e}")
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка при анализе: {e}")
|
||||
|
||||
|
||||
def generate_string(grammar: Grammar | None) -> None:
|
||||
if not grammar:
|
||||
print("Ошибка: Грамматика не загружена")
|
||||
return
|
||||
|
||||
try:
|
||||
terminals, rules = grammar.generate()
|
||||
generated_string = " ".join(terminals)
|
||||
print(f"Сгенерированная строка: {generated_string}")
|
||||
print(f"Применённые правила: {rules}")
|
||||
|
||||
# Сохраняем результат генерации в файл
|
||||
with open("generation_result.txt", "w", encoding="utf-8") as f:
|
||||
f.write(f"Generated string: {generated_string}\n")
|
||||
f.write("Applied rules: ")
|
||||
f.write(str(rules))
|
||||
f.write("\n\n")
|
||||
f.write("Derivation steps:\n")
|
||||
|
||||
derivation_steps = grammar.generate_derivation_steps(rules)
|
||||
for step in derivation_steps:
|
||||
f.write(f"{step}\n")
|
||||
|
||||
print("Подробный результат генерации сохранен в generation_result.txt")
|
||||
except Exception as e:
|
||||
print(f"Произошла ошибка при генерации: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
print("Программа для работы с LL(1)-грамматиками")
|
||||
print("=" * 60)
|
||||
print("Варианты команд:")
|
||||
print(" - load <файл> - загрузить грамматику из файла (по умолчанию grammar.txt)")
|
||||
print(" - check <строка> - проверить, соответствует ли строка грамматике")
|
||||
print(" - generate - сгенерировать случайную строку по грамматике")
|
||||
print(" - exit - выход из программы")
|
||||
print("=" * 60)
|
||||
|
||||
# Загружаем грамматику по умолчанию при старте
|
||||
grammar = load_grammar()
|
||||
|
||||
while True:
|
||||
command = input("\nВведите команду: ").strip()
|
||||
|
||||
if not command:
|
||||
continue
|
||||
|
||||
parts = command.split(maxsplit=1)
|
||||
cmd = parts[0].lower()
|
||||
|
||||
if cmd == "exit":
|
||||
print("Выход из программы.")
|
||||
break
|
||||
|
||||
elif cmd == "load":
|
||||
filename = "grammar.txt"
|
||||
if len(parts) > 1:
|
||||
filename = parts[1].strip()
|
||||
grammar = load_grammar(filename)
|
||||
|
||||
elif cmd == "check":
|
||||
input_string = ""
|
||||
if len(parts) > 1:
|
||||
input_string = parts[1].strip()
|
||||
check_string(grammar, input_string)
|
||||
|
||||
elif cmd == "generate":
|
||||
generate_string(grammar)
|
||||
|
||||
else:
|
||||
print(f"Неизвестная команда: {cmd}")
|
||||
print("Доступные команды: load, check, generate, exit")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
lab5/programm/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
prettytable==3.16.0
|
||||
wcwidth==0.2.13
|
||||