From 5bd140cd39970bae34de67145b11df04a455ec79 Mon Sep 17 00:00:00 2001 From: Arity-T Date: Tue, 20 May 2025 19:32:48 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B8?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC=D0=BC=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=BC=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab3/programm/analyze-ll-one.py | 617 ++++++++++++++++++++++++++++++++ lab3/programm/grammar.py | 34 +- lab3/programm/requirements.txt | 2 + lab3/report.tex | 112 +++++- 4 files changed, 737 insertions(+), 28 deletions(-) create mode 100644 lab3/programm/analyze-ll-one.py create mode 100644 lab3/programm/requirements.txt diff --git a/lab3/programm/analyze-ll-one.py b/lab3/programm/analyze-ll-one.py new file mode 100644 index 0000000..6f40d6d --- /dev/null +++ b/lab3/programm/analyze-ll-one.py @@ -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() diff --git a/lab3/programm/grammar.py b/lab3/programm/grammar.py index 05fd113..3a4d7dd 100644 --- a/lab3/programm/grammar.py +++ b/lab3/programm/grammar.py @@ -200,32 +200,24 @@ class Grammar: # Для каждого терминала 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 + 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]: - # Если 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._add_to_lookup_table(non_terminal, terminal, rule) - # Добавляем правило в таблицу - self.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 = [] diff --git a/lab3/programm/requirements.txt b/lab3/programm/requirements.txt new file mode 100644 index 0000000..0ba4bf9 --- /dev/null +++ b/lab3/programm/requirements.txt @@ -0,0 +1,2 @@ +prettytable==3.16.0 +wcwidth==0.2.13 diff --git a/lab3/report.tex b/lab3/report.tex index b896c9f..9c53f2b 100644 --- a/lab3/report.tex +++ b/lab3/report.tex @@ -153,6 +153,104 @@ Простое прошедшее время в немецком языке называется \textit{Претерит} или \textit{Претеритум} (нем. \textit{Präteritum} или нем. \textit{Imperfekt}). Это время не требует образования сложных конструкций, что позволяет относить его к простым формам. + \newpage + \section{Грамматика немецкого претеритума} + + \subsection{Общая характеристика претеритума} + Претеритум (Präteritum, Imperfekt) является одной из двух основных форм прошедшего времени в немецком языке, наряду с перфектом (Perfekt). В отличие от перфекта, претеритум представляет собой простую (синтетическую) форму, образуемую без вспомогательных глаголов, путём изменения основной формы глагола. + + Претеритум используется преимущественно в письменной речи, особенно в художественной литературе, исторических текстах и формальных документах. В разговорной речи он встречается редко, за исключением глаголов \textit{sein} (быть), \textit{haben} (иметь), модальных глаголов, а также в некоторых диалектах северной Германии. + + \subsection{Структура предложений в претеритуме} + Структура предложений в претеритуме соответствует общим правилам построения немецких предложений. Основные типы структур: + + \begin{enumerate} + \item \textbf{Прямой порядок слов} (главное предложение): + \begin{center} + \textit{Подлежащее} + \textit{глагол в претеритуме} + \textit{дополнения и обстоятельства} + \end{center} + Например: \textit{Der Mann las ein Buch.} (Мужчина читал книгу.) + + \item \textbf{Обратный порядок слов} (инверсия): + \begin{center} + \textit{Обстоятельство} + \textit{глагол в претеритуме} + \textit{подлежащее} + \textit{другие члены предложения} + \end{center} + Например: \textit{Gestern las der Mann ein Buch.} (Вчера мужчина читал книгу.) + + \item \textbf{Придаточное предложение}: + \begin{center} + \textit{..., союз} + \textit{подлежащее} + \textit{второстепенные члены} + \textit{глагол в претеритуме} + \end{center} + Например: \textit{Ich wusste, dass der Mann ein Buch las.} (Я знал, что мужчина читал книгу.) + \end{enumerate} + + \subsection{Образование форм претеритума} + + \subsubsection{Слабые (правильные) глаголы} + Образуются по формуле: основа глагола + суффикс \textit{-te} + личное окончание: + \begin{center} + \begin{tabular}{|l|l|} + \hline + \textbf{Лицо} & \textbf{Окончание (machen - делать)} \\ + \hline + ich & mach\textbf{te} \\ + du & mach\textbf{test} \\ + er/sie/es & mach\textbf{te} \\ + wir & mach\textbf{ten} \\ + ihr & mach\textbf{tet} \\ + sie/Sie & mach\textbf{ten} \\ + \hline + \end{tabular} + \end{center} + + \subsubsection{Сильные (неправильные) глаголы} + Образуются путём изменения корневой гласной без добавления суффикса \textit{-te}: + \begin{center} + \begin{tabular}{|l|l|} + \hline + \textbf{Лицо} & \textbf{Окончание (lesen - читать)} \\ + \hline + ich & \textbf{las} \\ + du & \textbf{las}st \\ + er/sie/es & \textbf{las} \\ + wir & \textbf{las}en \\ + ihr & \textbf{las}t \\ + sie/Sie & \textbf{las}en \\ + \hline + \end{tabular} + \end{center} + + \subsubsection{Смешанные глаголы} + Сочетают особенности сильных и слабых глаголов: меняют корневую гласную и получают суффикс \textit{-te}. + Например: \textit{bringen} (приносить) – \textit{brachte}, \textit{kennen} (знать) – \textit{kannte}. + + \subsection{Отрицание в претеритуме} + Отрицание в предложениях с претеритом образуется с помощью отрицательной частицы \textit{nicht}, которая обычно ставится после глагола и перед дополнениями: + + \textit{Der Mann las nicht ein Buch.} (Мужчина не читал книгу.) + + При отрицании всего предложения \textit{nicht} ставится в конце: + + \textit{Der Mann las ein Buch nicht.} (Мужчина не читал книгу [а что-то другое].) + + Размещение \textit{nicht} в конце предложения также часто используется в разговорной речи и может указывать на отрицание всего действия или ситуации в целом: + + \textit{Er kam gestern nicht.} (Он вчера не приходил.) + + \textit{Das Kind spielte draußen nicht.} (Ребенок не играл на улице.) + + \subsection{Особенности использования претеритума} + В немецком языке выбор между претеритумом и перфектом часто определяется: + + \begin{itemize} + \item \textbf{Стилем повествования:} претеритум более характерен для литературного стиля + \item \textbf{Типом текста:} в биографиях, исторических текстах, сказках преимущественно используется претеритум + \item \textbf{Региональными особенностями:} в Северной Германии претеритум используется чаще + \item \textbf{Типом глагола:} для глаголов \textit{sein}, \textit{haben} и модальных глаголов претеритум используется даже в разговорной речи + \end{itemize} + + В рамках данной работы мы рассматриваем формализованную грамматику, учитывающую основные структурные особенности предложений в претеритуме, без полного описания всех лексических и морфологических особенностей немецкого языка. + \newpage \section {Математическое описание} @@ -167,10 +265,10 @@ \item Рассматриваются три вида обстоятельств: обстоятельства времени, места и образа действия. В реальных предложениях языка могут встречаться и другие виды, но перечисленные являются наиболее часто встречающимися. \end{itemize} - \subsection{Грамматика в виде списка продукций} + \subsection{Пораждающая грамматика Хомского} \label{subsec:grammar} - Формально можно описать грамматику в виде списка продукций: + Формально пораждающую грамматику Хомского можно задать списком продукций: \begin{verbatim} Предложение -> Повествовательное "." @@ -415,7 +513,7 @@ class Grammar: Метод принимает ссылку на объект класса Grammar и строку text типа str, содержащую текстовое представление грамматики. - Метод не возвращает значений, но заполняет словарь productions и устанавливает переменную start\_symbol в объекте класса. + Метод не возвращает значений явно, но заполняет словарь productions и устанавливает переменную start\_symbol в объекте класса. \begin{lstlisting}[caption={Код метода \texttt{\_parse\_productions}.}, label={lst:parse_productions}] def _parse_productions(self, text: str): @@ -450,7 +548,7 @@ def _parse_productions(self, text: str): Метод принимает ссылку на объект класса Grammar. - Метод не возвращает значений, но заполняет словарь first\_sets в объекте класса. + Метод не возвращает значений явно, но заполняет словарь first\_sets в объекте класса. \begin{lstlisting}[caption={Код метода \texttt{\_calculate\_first\_sets}.}, label={lst:calculate_first_sets}] def _calculate_first_sets(self): @@ -503,7 +601,7 @@ def _calculate_first_sets(self): Метод принимает ссылку на объект класса Grammar. - Метод не возвращает значений, но заполняет словарь follow\_sets в объекте класса. + Метод не возвращает значений явно, но заполняет словарь follow\_sets в объекте класса. \begin{lstlisting}[caption={Код метода \texttt{\_calculate\_follow\_sets}.}, label={lst:calculate_follow_sets}] def _calculate_follow_sets(self): @@ -602,7 +700,7 @@ def _first_of_sequence(self, sequence): Метод принимает ссылку на объект класса Grammar. - Метод не возвращает значений, но заполняет словарь lookup\_table в объекте класса или вызывает исключение, если грамматика не является LL(1). + Метод не возвращает значений явно, но заполняет словарь lookup\_table в объекте класса или вызывает исключение, если грамматика не является LL(1). \begin{lstlisting}[caption={Код метода \texttt{\_fill\_lookup\_table}.}, label={lst:fill_lookup_table}] def _fill_lookup_table(self): @@ -976,7 +1074,7 @@ def main(): \addcontentsline{toc}{section}{Заключение} В ходе выполнения лабораторной работы была построена контекстно-свободная грамматика для подмножества немецкого языка, описывающая простое прошедшее время Претерит. На основе разработанной грамматики была реализована программа, которая проверяет принадлежность входной строки заданному языку и генерирует случайные корректные предложения. Для анализа предложений использовался алгоритм LL(1)-разбора, основанный на построении множеств FIRST и FOLLOW для всех нетерминалов грамматики и создании таблицы синтаксического анализа. - Из достоинств выполнения лабораторной работы можно выделить возможность задания грамматики в отдельном текстовом файле, что позволяет легко изменять и расширять её без модификации программного кода. Использование объектно-ориентированного подхода в реализации обеспечило хорошую структуру кода. Вся логика работы с грамматикой реализована в классе Grammar, а обработка пользовательского ввода вынесена в функцию main. + Из достоинств выполнения лабораторной работы можно выделить возможность задания грамматики в отдельном текстовом файле, что позволяет легко изменять и расширять её без модификации программного кода. Также программа автоматически проверяет, что введенная грамматика является LL(1)-грамматикой. В противном случае, программа выводит сообщение об ошибке, в указывается на конкретные правила грамматики, между выбором которых возникает неоднозначность. К недостаткам текущей реализации можно отнести ограниченность словарного запаса, что сужает разнообразие генерируемых предложений. Также алгоритм генерации не контролирует длину предложений, что может приводить к избыточно длинным или коротким конструкциям. В текущей версии система не учитывает некоторые грамматические особенности немецкого языка, например, склонение прилагательных и согласование артиклей с родом существительных.