Правки и программа в одном файле
This commit is contained in:
617
lab3/programm/analyze-ll-one.py
Normal file
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()
|
||||||
@@ -200,31 +200,23 @@ class Grammar:
|
|||||||
|
|
||||||
# Для каждого терминала a в First(w)
|
# Для каждого терминала a в First(w)
|
||||||
for terminal in first_of_rule - {""}:
|
for terminal in first_of_rule - {""}:
|
||||||
# Если T[A,a] уже содержит правило, грамматика не LL(1)
|
self._add_to_lookup_table(non_terminal, terminal, rule)
|
||||||
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)
|
# Если эпсилон в First(w), то для каждого b в Follow(A)
|
||||||
if "" in first_of_rule:
|
if "" in first_of_rule:
|
||||||
for terminal in self.follow_sets[non_terminal]:
|
for terminal in self.follow_sets[non_terminal]:
|
||||||
# Если T[A,b] уже содержит правило, грамматика не LL(1)
|
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]:
|
if terminal in self.lookup_table[non_terminal]:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Грамматика не является LL(1)-грамматикой.\n"
|
"\nГрамматика не является LL(1)-грамматикой.\n"
|
||||||
f"Конфликт в ячейке [{non_terminal}, {terminal}]\n"
|
f'Распознаваемый нетерминал: "{non_terminal}"\n'
|
||||||
f"Новое правило: {non_terminal} -> {' '.join(rule)};\n"
|
f'Поступающий на вход терминал: "{terminal}"\n'
|
||||||
f"Существующее правило: {non_terminal} -> {self.lookup_table[non_terminal][terminal]}"
|
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
|
self.lookup_table[non_terminal][terminal] = rule
|
||||||
|
|
||||||
def format_rules(self) -> str:
|
def format_rules(self) -> str:
|
||||||
|
|||||||
2
lab3/programm/requirements.txt
Normal file
2
lab3/programm/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
prettytable==3.16.0
|
||||||
|
wcwidth==0.2.13
|
||||||
112
lab3/report.tex
112
lab3/report.tex
@@ -153,6 +153,104 @@
|
|||||||
|
|
||||||
Простое прошедшее время в немецком языке называется \textit{Претерит} или \textit{Претеритум} (нем. \textit{Präteritum} или нем. \textit{Imperfekt}). Это время не требует образования сложных конструкций, что позволяет относить его к простым формам.
|
Простое прошедшее время в немецком языке называется \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
|
\newpage
|
||||||
\section {Математическое описание}
|
\section {Математическое описание}
|
||||||
@@ -167,10 +265,10 @@
|
|||||||
\item Рассматриваются три вида обстоятельств: обстоятельства времени, места и образа действия. В реальных предложениях языка могут встречаться и другие виды, но перечисленные являются наиболее часто встречающимися.
|
\item Рассматриваются три вида обстоятельств: обстоятельства времени, места и образа действия. В реальных предложениях языка могут встречаться и другие виды, но перечисленные являются наиболее часто встречающимися.
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
\subsection{Грамматика в виде списка продукций}
|
\subsection{Пораждающая грамматика Хомского}
|
||||||
\label{subsec:grammar}
|
\label{subsec:grammar}
|
||||||
|
|
||||||
Формально можно описать грамматику в виде списка продукций:
|
Формально пораждающую грамматику Хомского можно задать списком продукций:
|
||||||
|
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
Предложение -> Повествовательное "."
|
Предложение -> Повествовательное "."
|
||||||
@@ -415,7 +513,7 @@ class Grammar:
|
|||||||
|
|
||||||
Метод принимает ссылку на объект класса Grammar и строку text типа str, содержащую текстовое представление грамматики.
|
Метод принимает ссылку на объект класса Grammar и строку text типа str, содержащую текстовое представление грамматики.
|
||||||
|
|
||||||
Метод не возвращает значений, но заполняет словарь productions и устанавливает переменную start\_symbol в объекте класса.
|
Метод не возвращает значений явно, но заполняет словарь productions и устанавливает переменную start\_symbol в объекте класса.
|
||||||
|
|
||||||
\begin{lstlisting}[caption={Код метода \texttt{\_parse\_productions}.}, label={lst:parse_productions}]
|
\begin{lstlisting}[caption={Код метода \texttt{\_parse\_productions}.}, label={lst:parse_productions}]
|
||||||
def _parse_productions(self, text: str):
|
def _parse_productions(self, text: str):
|
||||||
@@ -450,7 +548,7 @@ def _parse_productions(self, text: str):
|
|||||||
|
|
||||||
Метод принимает ссылку на объект класса Grammar.
|
Метод принимает ссылку на объект класса Grammar.
|
||||||
|
|
||||||
Метод не возвращает значений, но заполняет словарь first\_sets в объекте класса.
|
Метод не возвращает значений явно, но заполняет словарь first\_sets в объекте класса.
|
||||||
|
|
||||||
\begin{lstlisting}[caption={Код метода \texttt{\_calculate\_first\_sets}.}, label={lst:calculate_first_sets}]
|
\begin{lstlisting}[caption={Код метода \texttt{\_calculate\_first\_sets}.}, label={lst:calculate_first_sets}]
|
||||||
def _calculate_first_sets(self):
|
def _calculate_first_sets(self):
|
||||||
@@ -503,7 +601,7 @@ def _calculate_first_sets(self):
|
|||||||
|
|
||||||
Метод принимает ссылку на объект класса Grammar.
|
Метод принимает ссылку на объект класса Grammar.
|
||||||
|
|
||||||
Метод не возвращает значений, но заполняет словарь follow\_sets в объекте класса.
|
Метод не возвращает значений явно, но заполняет словарь follow\_sets в объекте класса.
|
||||||
|
|
||||||
\begin{lstlisting}[caption={Код метода \texttt{\_calculate\_follow\_sets}.}, label={lst:calculate_follow_sets}]
|
\begin{lstlisting}[caption={Код метода \texttt{\_calculate\_follow\_sets}.}, label={lst:calculate_follow_sets}]
|
||||||
def _calculate_follow_sets(self):
|
def _calculate_follow_sets(self):
|
||||||
@@ -602,7 +700,7 @@ def _first_of_sequence(self, sequence):
|
|||||||
|
|
||||||
Метод принимает ссылку на объект класса Grammar.
|
Метод принимает ссылку на объект класса Grammar.
|
||||||
|
|
||||||
Метод не возвращает значений, но заполняет словарь lookup\_table в объекте класса или вызывает исключение, если грамматика не является LL(1).
|
Метод не возвращает значений явно, но заполняет словарь lookup\_table в объекте класса или вызывает исключение, если грамматика не является LL(1).
|
||||||
|
|
||||||
\begin{lstlisting}[caption={Код метода \texttt{\_fill\_lookup\_table}.}, label={lst:fill_lookup_table}]
|
\begin{lstlisting}[caption={Код метода \texttt{\_fill\_lookup\_table}.}, label={lst:fill_lookup_table}]
|
||||||
def _fill_lookup_table(self):
|
def _fill_lookup_table(self):
|
||||||
@@ -976,7 +1074,7 @@ def main():
|
|||||||
\addcontentsline{toc}{section}{Заключение}
|
\addcontentsline{toc}{section}{Заключение}
|
||||||
В ходе выполнения лабораторной работы была построена контекстно-свободная грамматика для подмножества немецкого языка, описывающая простое прошедшее время Претерит. На основе разработанной грамматики была реализована программа, которая проверяет принадлежность входной строки заданному языку и генерирует случайные корректные предложения. Для анализа предложений использовался алгоритм LL(1)-разбора, основанный на построении множеств FIRST и FOLLOW для всех нетерминалов грамматики и создании таблицы синтаксического анализа.
|
В ходе выполнения лабораторной работы была построена контекстно-свободная грамматика для подмножества немецкого языка, описывающая простое прошедшее время Претерит. На основе разработанной грамматики была реализована программа, которая проверяет принадлежность входной строки заданному языку и генерирует случайные корректные предложения. Для анализа предложений использовался алгоритм LL(1)-разбора, основанный на построении множеств FIRST и FOLLOW для всех нетерминалов грамматики и создании таблицы синтаксического анализа.
|
||||||
|
|
||||||
Из достоинств выполнения лабораторной работы можно выделить возможность задания грамматики в отдельном текстовом файле, что позволяет легко изменять и расширять её без модификации программного кода. Использование объектно-ориентированного подхода в реализации обеспечило хорошую структуру кода. Вся логика работы с грамматикой реализована в классе Grammar, а обработка пользовательского ввода вынесена в функцию main.
|
Из достоинств выполнения лабораторной работы можно выделить возможность задания грамматики в отдельном текстовом файле, что позволяет легко изменять и расширять её без модификации программного кода. Также программа автоматически проверяет, что введенная грамматика является LL(1)-грамматикой. В противном случае, программа выводит сообщение об ошибке, в указывается на конкретные правила грамматики, между выбором которых возникает неоднозначность.
|
||||||
|
|
||||||
К недостаткам текущей реализации можно отнести ограниченность словарного запаса, что сужает разнообразие генерируемых предложений. Также алгоритм генерации не контролирует длину предложений, что может приводить к избыточно длинным или коротким конструкциям. В текущей версии система не учитывает некоторые грамматические особенности немецкого языка, например, склонение прилагательных и согласование артиклей с родом существительных.
|
К недостаткам текущей реализации можно отнести ограниченность словарного запаса, что сужает разнообразие генерируемых предложений. Также алгоритм генерации не контролирует длину предложений, что может приводить к избыточно длинным или коротким конструкциям. В текущей версии система не учитывает некоторые грамматические особенности немецкого языка, например, склонение прилагательных и согласование артиклей с родом существительных.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user