From 529da8d98ed7db7f5ea905ac605ca3053527bd69 Mon Sep 17 00:00:00 2001 From: Arity-T Date: Tue, 6 May 2025 15:40:22 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D0=BF=D0=BE=D0=B7=D0=BD?= =?UTF-8?q?=D0=B0=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B8=20ll(1)=20=D1=82?= =?UTF-8?q?=D0=B5=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab3/.gitignore | 8 + lab3/programm/.gitignore | 2 + lab3/programm/grammar.py | 318 ++++++++++++++++++++++++++ lab3/programm/grammar.txt | 25 +++ lab3/programm/main.py | 61 +++++ lab3/report.tex | 462 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 876 insertions(+) create mode 100644 lab3/.gitignore create mode 100644 lab3/programm/.gitignore create mode 100644 lab3/programm/grammar.py create mode 100644 lab3/programm/grammar.txt create mode 100644 lab3/programm/main.py create mode 100644 lab3/report.tex diff --git a/lab3/.gitignore b/lab3/.gitignore new file mode 100644 index 0000000..96dc833 --- /dev/null +++ b/lab3/.gitignore @@ -0,0 +1,8 @@ +**/* +!.gitignore +!report.tex +!img +!img/** +!programm +!programm/*.py +!programm/*.txt \ No newline at end of file diff --git a/lab3/programm/.gitignore b/lab3/programm/.gitignore new file mode 100644 index 0000000..5f597ba --- /dev/null +++ b/lab3/programm/.gitignore @@ -0,0 +1,2 @@ +grammar_* +analysis_* diff --git a/lab3/programm/grammar.py b/lab3/programm/grammar.py new file mode 100644 index 0000000..02db6fe --- /dev/null +++ b/lab3/programm/grammar.py @@ -0,0 +1,318 @@ +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 - {""}: + # Если T[A,a] уже содержит правило, грамматика не LL(1) + if terminal in self.lookup_table[non_terminal]: + raise ValueError( + "Грамматика не является LL(1)-грамматикой.\n" + f"Конфликт в ячейке [{non_terminal}, {terminal}]\n" + f"Новое правило: {non_terminal} -> {' '.join(rule)};\n" + f"Существующее правило: {non_terminal} -> {self.lookup_table[non_terminal][terminal]}" + ) + + # Добавляем правило в таблицу + self.lookup_table[non_terminal][terminal] = rule + + # Если эпсилон в First(w), то для каждого b в Follow(A) + if "" in first_of_rule: + for terminal in self.follow_sets[non_terminal]: + # Если T[A,b] уже содержит правило, грамматика не LL(1) + if terminal in self.lookup_table[non_terminal]: + raise ValueError( + "Грамматика не является LL(1)-грамматикой.\n" + f"Конфликт в ячейке [{non_terminal}, {terminal}]\n" + f"Новое правило: {non_terminal} -> {' '.join(rule)};\n" + f"Существующее правило: {non_terminal} -> {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 analyze(self, string: str) -> list[int]: + input_tokens = string.split() + ["$"] + 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 diff --git a/lab3/programm/grammar.txt b/lab3/programm/grammar.txt new file mode 100644 index 0000000..bb6f1a6 --- /dev/null +++ b/lab3/programm/grammar.txt @@ -0,0 +1,25 @@ +Предложение -> ПрямойПорядок | Инверсия +ПрямойПорядок -> Подлежащее ДополнениеКПодлежащему Глагол ВторостепенныеЧлены Отрицание +Инверсия -> Обстоятельство Глагол Подлежащее ВторостепенныеЧлены Отрицание +Подлежащее -> ИменнаяГруппа ПридаточноеПредложение | Местоимение +ПридаточноеПредложение -> ", " Союз Подлежащее Глагол ВторостепенныеЧлены Отрицание ", " | 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" \ No newline at end of file diff --git a/lab3/programm/main.py b/lab3/programm/main.py new file mode 100644 index 0000000..bb4addd --- /dev/null +++ b/lab3/programm/main.py @@ -0,0 +1,61 @@ +import json + +from grammar import Grammar + +with open("grammar.txt", "r", encoding="utf-8") as file: + text = file.read() +# text = """ +# S -> A b B | d +# A -> C A b | B +# B -> c S d | epsilon +# C -> a | e d +# """ +# text = """ +# S -> F | ( S + F ) +# F -> 1 +# """ +grammar = Grammar(text) + +print("FIRST sets:", grammar.first_sets) +print("FOLLOW sets:", grammar.follow_sets) + +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") + +input_string = "ich las gestern ein alt Buch" +input_string = "der alt Mann las ein Buch" +input_string = "gestern las der alt Mann ein Buch" +print(f"Analyzing input '{input_string}':") +parse_result = grammar.analyze(input_string) +print(f"Applied rules: {parse_result}") + +rule_details = {num: rule for rule, num in grammar.rule_numbers.items()} + +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") + + current = grammar.start_symbol + f.write(f"{current}\n") + + for rule_num in parse_result: + 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) + f.write(f"{current}\n") + +print("Результат анализа сохранен в analysis_result.txt") diff --git a/lab3/report.tex b/lab3/report.tex new file mode 100644 index 0000000..113645b --- /dev/null +++ b/lab3/report.tex @@ -0,0 +1,462 @@ +\documentclass[a4paper, final]{article} +%\usepackage{literat} % Нормальные шрифты +\usepackage[14pt]{extsizes} % для того чтобы задать нестандартный 14-ый размер шрифта +\usepackage{tabularx} +\usepackage[T2A]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage[russian]{babel} +\usepackage{amsmath} +\usepackage[left=25mm, top=20mm, right=20mm, bottom=20mm, footskip=10mm]{geometry} +\usepackage{ragged2e} %для растягивания по ширине +\usepackage{setspace} %для межстрочно го интервала +\usepackage{moreverb} %для работы с листингами +\usepackage{indentfirst} % для абзацного отступа +\usepackage{moreverb} %для печати в листинге исходного кода программ +\usepackage{pdfpages} %для вставки других pdf файлов +\usepackage{tikz} +\usepackage{graphicx} +\usepackage{afterpage} +\usepackage{longtable} +\usepackage{float} + + + +% \usepackage[paper=A4,DIV=12]{typearea} +\usepackage{pdflscape} +% \usepackage{lscape} + +\usepackage{array} +\usepackage{multirow} + +\renewcommand\verbatimtabsize{4\relax} +\renewcommand\listingoffset{0.2em} %отступ от номеров строк в листинге +\renewcommand{\arraystretch}{1.4} % изменяю высоту строки в таблице +\usepackage[font=small, singlelinecheck=false, justification=centering, format=plain, labelsep=period]{caption} %для настройки заголовка таблицы +\usepackage{listings} %листинги +\usepackage{xcolor} % цвета +\usepackage{hyperref}% для гиперссылок +\usepackage{enumitem} %для перечислений + +\newcommand{\specialcell}[2][l]{\begin{tabular}[#1]{@{}l@{}}#2\end{tabular}} + + +\setlist[enumerate,itemize]{leftmargin=1.2cm} %отступ в перечислениях + +\hypersetup{colorlinks, + allcolors=[RGB]{010 090 200}} %красивые гиперссылки (не красные) + +% подгружаемые языки — подробнее в документации listings (это всё для листингов) +\lstloadlanguages{ SQL} +% включаем кириллицу и добавляем кое−какие опции +\lstset{tabsize=2, + breaklines, + basicstyle=\footnotesize, + columns=fullflexible, + flexiblecolumns, + numbers=left, + numberstyle={\footnotesize}, + keywordstyle=\color{blue}, + inputencoding=cp1251, + extendedchars=true +} +\lstdefinelanguage{MyC}{ + language=SQL, +% ndkeywordstyle=\color{darkgray}\bfseries, +% identifierstyle=\color{black}, +% morecomment=[n]{/**}{*/}, +% commentstyle=\color{blue}\ttfamily, +% stringstyle=\color{red}\ttfamily, +% morestring=[b]", +% showstringspaces=false, +% morecomment=[l][\color{gray}]{//}, + keepspaces=true, + escapechar=\%, + texcl=true +} + +\textheight=24cm % высота текста +\textwidth=16cm % ширина текста +\oddsidemargin=0pt % отступ от левого края +\topmargin=-1.5cm % отступ от верхнего края +\parindent=24pt % абзацный отступ +\parskip=5pt % интервал между абзацами +\tolerance=2000 % терпимость к "жидким" строкам +\flushbottom % выравнивание высоты страниц + + +% Настройка листингов +\lstset{ + language=python, + extendedchars=\true, + inputencoding=utf8, + keepspaces=true, + % captionpos=b, % подписи листингов снизу +} + +\begin{document} % начало документа + + + + % НАЧАЛО ТИТУЛЬНОГО ЛИСТА + \begin{center} + \hfill \break + \hfill \break + \normalsize{МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ РОССИЙСКОЙ ФЕДЕРАЦИИ\\ + федеральное государственное автономное образовательное учреждение высшего образования «Санкт-Петербургский политехнический университет Петра Великого»\\[10pt]} + \normalsize{Институт компьютерных наук и кибербезопасности}\\[10pt] + \normalsize{Высшая школа технологий искусственного интеллекта}\\[10pt] + \normalsize{Направление: 02.03.01 <<Математика и компьютерные науки>>}\\ + + \hfill \break + \hfill \break + \hfill \break + \hfill \break + \large{Лабораторная работа №3}\\ + \large{<<Грамматика простого прошедшего времени}\\ + \large{немецкого языка>>}\\ + \large{по дисциплине}\\ + \large{<<Математическая логика и теория автоматов>>}\\ + \large{Вариант 15}\\ + + % \hfill \break + \hfill \break + \end{center} + + \small{ + \begin{tabular}{lrrl} + \!\!\!Студент, & \hspace{2cm} & & \\ + \!\!\!группы 5130201/20102 & \hspace{2cm} & \underline{\hspace{3cm}} &Тищенко А. А. \\\\ + \!\!\!Преподаватель & \hspace{2cm} & \underline{\hspace{3cm}} & Востров А. В. \\\\ + &&\hspace{4cm} + \end{tabular} + \begin{flushright} + <<\underline{\hspace{1cm}}>>\underline{\hspace{2.5cm}} 2025г. + \end{flushright} + } + + \hfill \break + % \hfill \break + \begin{center} \small{Санкт-Петербург, 2025} \end{center} + \thispagestyle{empty} % выключаем отображение номера для этой страницы + + % КОНЕЦ ТИТУЛЬНОГО ЛИСТА + \newpage + + \tableofcontents + + + \newpage + + \section*{Введение} + \addcontentsline{toc}{section}{Введение} + Лабораторная №3 по дисциплине <<Математическая логика>> заключается в построении контексно-свободной грамматики подмножества естественного языка. В данном варианте рассматривается грамматика простого прошедшего времени немецкого языка. + + Простое прошедшее время в немецком языке называется \textit{Претерит} или \textit{Претеритум} (нем. \textit{Präteritum} или нем. \textit{Imperfekt}). \textit{Претеритум} служит для передачи действия в прошлом. Это время не требует образования сложных конструкций, что позволяет относить его к простым формам. + + + + \newpage + \section {Математическое описание} + \subsection{КС-грамматика} + + \begin{verbatim} + Предложение -> ПрямойПорядок | Инверсия + ПрямойПорядок -> Подлежащее ДополнениеКПодлежащему Глагол + ВторостепенныеЧлены Отрицание + Инверсия -> Обстоятельство Глагол Подлежащее + ВторостепенныеЧлены Отрицание + Подлежащее -> ИменнаяГруппа ПридаточноеПредложение | Местоимение + ПридаточноеПредложение -> ", " Союз Подлежащее Глагол + ВторостепенныеЧлены Отрицание ", " | + 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" + \end{verbatim} + + \subsection{БНФ-нотация} + + % \begin{verbatim} + % Предложение ::= ПрямойПорядок | Инверсия + + % ПрямойПорядок ::= Подлежащее [ПридаточноеПредложение] + % Глагол {ВторостепенныйЧлен} [Отрицание] + + % ПридаточноеПредложение ::= ", " Союз Подлежащее [ПридаточноеПредложение] + % Глагол [Отрицание] ", " + + % Инверсия ::= Обстоятельство Глагол Подлежащее + % [ПридаточноеПредложение] {ВторостепенныйЧлен} [Отрицание] + + % Обстоятельство ::= ОбстоятельствоВремени | + % ОбстоятельствоМеста | + % ОбстоятельствоОбразаДействия + + % ВторостепенныйЧлен ::= Обстоятельство | + % КосвенноеДополнение | + % ПрямоеДополнение + + % Подлежащее ::= Местоимение | ИменнаяГруппа + + % ИменнаяГруппа ::= [Артикль | ПритяжательноеМестоимение] + % {Прилагательное} Существительное + % [ДополнениеКПодлежащему] + + % ДополнениеКПодлежащему ::= Предлог Существительное + + % КосвенноеДополнение ::= Предлог ИменнаяГруппа + + % ПрямоеДополнение ::= [Артикль | ПритяжательноеМестоимение] + % {Прилагательное} Существительное + + % ОбстоятельствоМеста ::= Предлог [Артикль | ПритяжательноеМестоимение] + % Существительное + + % Союз ::= "welcher" (которого) | "welche" (которую) + % | "welches" (которого, ср. р) + + % Отрицание ::= "nicht" + + % Местоимение ::= "ich" (я) | "du" (ты) | "er" (он) | "sie" (она/они) | + % "es" (оно) | "wir" (мы) | "ihr" (вы) | "Sie" (Вы) + + % ПритяжательноеМестоимение ::= "mein" (мой) | "dein" (твой) | + % "sein" (его) | "ihr" (её) | "unser" (наш) | + % "euer" (ваш) | "ihre" (их) + + % Артикль ::= "der" | "die" | "das" | "ein" | + % "eine" | "einen" | "einem" | "einer" + + % Прилагательное ::= "alt" (старый) | "jung" (молодой) | + % "groß" (большой) | "klein" (маленький) | "gut" (хороший) | + % "schlecht" (плохой) | "schnell" (быстрый) | "langsam" (медленный) + + % Существительное ::= "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" (делал) + + % Инфинитив ::= "lesen" (читать) | "schreiben" (писать) | + % "kochen" (готовить) | "essen" (есть) | + % "gehen" (идти) | "kommen" (приходить) | + % "sagen" (говорить) | "machen" (делать) + + % Причастие ::= "gelesen" (прочитанный) | "geschrieben" (написанный) | + % "gekocht" (приготовленный) | "gegessen" (съеденный) | + % "gegangen" (ушедший) | "gekommen" (пришедший) | + % "gesagt" (сказанный) | "gemacht" (сделанный) + + % \end{verbatim} + + \subsection{Примеры предложений} + + Ich las gestern ein altes Buch + + Der alte Mann las ein Buch + + + + + + \subsection{Примеры предложений} + + \textbf{Пример 1: Der alte Mann las ein Buch.} (Старый мужчина читал книгу.) + + \begin{tabularx}{\textwidth}{|l|l|l|X|} + \hline + Позиция & Компонент & Элемент & Разбор по БНФ \\ + \hline + 1 & Подлежащее & der alte Mann & ИменнаяГруппа → Артикль + Прилагательное + Существительное \\ + \hline + 2 & Сказуемое & las & Глагол \\ + \hline + 3 & Второстепенные члены & ein Buch & ПрямоеДополнение → Артикль + Существительное \\ + \hline + \end{tabularx} + + \vspace{0.5cm} + + \textbf{Пример 2: Gestern las der alte Mann ein Buch.} (Вчера старый мужчина читал книгу.) + + \begin{tabularx}{\textwidth}{|l|l|l|X|} + \hline + Позиция & Компонент & Элемент & Разбор по БНФ \\ + \hline + 1 & Обстоятельство & Gestern & ОбстоятельствоВремени → НаречиеВремени \\ + \hline + 2 & Сказуемое & las & Глагол \\ + \hline + 3 & Подлежащее & der alte Mann & ИменнаяГруппа → Артикль + Прилагательное + Существительное \\ + \hline + 4 & Второстепенные члены & ein Buch & ПрямоеДополнение → Артикль + Существительное \\ + \hline + \end{tabularx} + + \vspace{0.5cm} + + \textbf{Пример 3: Mein Freund aus Berlin schrieb mir gestern einen Brief.} + + (Мой друг из Берлина написал мне вчера письмо.) + + \begin{tabularx}{\textwidth}{|l|l|l|X|} + \hline + Позиция & Компонент & Элемент & Разбор по БНФ \\ + \hline + 1 & Подлежащее & Mein Freund aus Berlin & ИменнаяГруппа → ПритяжательноеМестоимение + Существительное + ДополнениеКПодлежащему (Предлог + Существительное) \\ + \hline + 2 & Сказуемое & schrieb & ПростойГлагол ВПретерите \\ + \hline + 3 & Второстепенные члены & mir & Косвенное Дополнение → Местоимение \\ + \hline + 4 & Второстепенные члены & gestern & Обстоятельство Времени → НаречиеВремени \\ + \hline + 5 & Второстепенные члены & einen Brief & ПрямоеДополнение → Артикль + Существительное \\ + \hline + \end{tabularx} + + \vspace{0.5cm} + + \textbf{Пример 4: Am Abend kochte sie schnell das Abendessen in der Küche.} + + (Вечером она быстро приготовила ужин на кухне.) + + \begin{tabularx}{\textwidth}{|l|l|l|X|} + \hline + Позиция & Компонент & Элемент & Разбор по БНФ \\ + \hline + 1 & Обстоятельство & Am Abend & ОбстоятельствоВремени → Предлог + Существительное \\ + \hline + 2 & Сказуемое & kochte & Глагол \\ + \hline + 3 & Подлежащее & sie & Местоимение \\ + \hline + 4 & Второстепенные члены & schnell & ОбстоятельствоОбразаДействия \\ + \hline + 5 & Второстепенные члены & das Abendessen & ПрямоеДополнение → Артикль + Существительное \\ + \hline + 6 & Второстепенные члены & in der Küche & ОбстоятельствоМеста → Предлог + Артикль + Существительное \\ + \hline + \end{tabularx} + + \vspace{0.5cm} + + \textbf{Пример 5: Er wollte ein Buch lesen.} (Он хотел прочитать книгу.) + + \begin{tabularx}{\textwidth}{|l|l|l|X|} + \hline + Позиция & Компонент & Элемент & Разбор по БНФ \\ + \hline + 1 & Подлежащее & Er & Местоимение \\ + \hline + 2 & Сказуемое & wollte lesen & СоставноеСказуемое → МодальныйГлаголВПретерите + Инфинитив \\ + \hline + 3 & Второстепенные члены & ein Buch & ПрямоеДополнение → Артикль + Существительное \\ + \hline + \end{tabularx} + + + \newpage + \section{Особенности реализации} + + + \newpage + \section{Результаты работы программы} + % Результаты работы программы представлены на Рис.~\ref{fig:result1}. + + % \begin{figure}[h!] + % \centering + % \includegraphics[width=1\linewidth]{img/result1.png} + % \caption{Результаты работы программы.} + % \label{fig:result1} + % \end{figure} + + % \newpage + + % \begin{figure}[h!] + % \centering + % \includegraphics[width=0.8\linewidth]{img/wrong.png} + % \caption{Реакция программы на некорректный пользовательский ввод.} + % \label{fig:wrong} + % \end{figure} + + % На Рис.~\ref{fig:wrong} представлена реакция программы на некорректный пользовательский ввод. + + + \newpage + \section*{Заключение} + \addcontentsline{toc}{section}{Заключение} + В ходе выполнения лабораторной работы было построено регулярное выражение для распознавания различных форматов вещественных чисел. В соответствии с теоремой Клини по заданному регулярному выражению, задающему регулярный + язык, был построен недетерминированный конечный автомат-распознаватель. Затем полученный конечный автомат был детерминирован. На основе разработанного автомата была реализована программа, которая проверяет соответствие входной строки заданному формату и генерирует случайные корректные строки. + + Из достоинств выполнения лабораторной работы можно выделить структурирование кода за счёт использования ООП. Вся логика работы с конечными автоматами вынесена в отдельный класс \texttt{FiniteAutomaton} с четко разделенными методами для проверки строк и генерации случайных строк. Создана удобная интерактивная консольная оболочка для взаимодействия с пользователем, позволяющая выполнять различные команды. + + К недостаткам текущей реализации можно отнести следующие аспекты. Во-первых, переходы в автомате представлены в виде строк, содержащих допустимые символы, такой способ представления переходов не является самым оптимальным с точки зрения производительности. Во-вторых, в реализации генерации случайных строк вероятность остановки одинакова для всех финальных состояний, что может приводить к неравномерному распределению различных форматов чисел в генерируемых строках. + + Функционал программы несложно масштабировать. Класс \texttt{FiniteAutomaton} может быть использован для работы с различными конечными автоматами-распознавателями. Для изменения распознаваемого языка достаточно задать новые параметры для автомата, не меняя базовую логику программы. Однако, текущая реализация работает только с символьными переходами, поэтому задать строчные переходы в виде, например, регулярных выражений не представляется возможным. Однако подобный функционал также несложно реализовать, взяв за основу существующий код. + + На выполнение лабораторной работы ушло около 10 часов. Работа была выполнена в среде разработки Visual Studio Code. Программа написана на Python версии 3.10. + +\newpage +\section*{Список литературы} +\addcontentsline{toc}{section}{Список литературы} + +\vspace{-1.5cm} +\begin{thebibliography}{0} + \bibitem{vostrov} + Востров, А.В. Курс лекций по дисциплине <<Математическая логика>>. URL \url{https://tema.spbstu.ru/compiler/} (дата обращения 01.04.2025 г.) + \bibitem{lutz} + Лутц, М. Изучаем Python. 5-е изд. / М. Лутц. — СПб.: Питер, 2019. — 1216 с. + \bibitem{friedl} + Фридл, Дж. Регулярные выражения = Mastering Regular Expressions / Дж. Фридл. — СПб.: Питер, 2001. — 352 с. — (Библиотека программиста). +\end{thebibliography} + +\end{document} \ No newline at end of file