Скачиваний:
0
Добавлен:
15.04.2026
Размер:
19.44 Кб
Скачать
import collections
import pprint
from typing import Set, Dict, Tuple, List, Union


class Rule:
    """
    Класс "Правило"
    Пример: S -> F F
    self.nonterminal = "S"
    self.rule_tuple = ("F", "F")
    """
    
    def __init__(self,
                 syntax_bnf: Dict[str, List[Tuple[str]]],
                 first_sets: Dict[str, Set[str]],
                 nonterminal: str,
                 rule: Tuple[str]):
        """
        Конструктор
        :param syntax_bnf: синтаксис языка в обычной (не расширенной) форме Бэкуса-Наура
        :param first_sets: словарь, нетерминал -> множество терминалов,
                                    которые могут появиться первыми при полном выводе
        :param nonterminal: нетерминал
        :param rule: правило
        """
        self.syntax_bnf = syntax_bnf  # ссылка (предполагается неизменяемость)
        self.first_sets = first_sets  # ссылка (предполагается неизменяемость)
        self.nonterminal = nonterminal  # ссылка (str -- non immutable)
        self.rule_tuple = rule  # ссылка (tuple -- non immutable)
    
    def get_sequence_firsts(self, sequence: Tuple[str]) -> Set[str]:
        """
        Возвращает множество терминалов, которые могут появиться первыми при полном выводе sequence
        :param sequence: упорядоченный список терминалов и нетерминалов
        :return: множество терминалов, которые могут появиться первыми при полном выводе sequence
        """
        sequence_firsts = set()
        epsilon_in_symbol_firsts = True
        for symbol in sequence:
            epsilon_in_symbol_firsts = False
            if symbol not in self.syntax_bnf:  # если symbol нетерминал
                sequence_firsts.add(symbol)
                break
            for first in self.first_sets[symbol]:
                if first != '':
                    sequence_firsts.add(first)
                else:
                    epsilon_in_symbol_firsts = True
            epsilon_in_symbol_firsts |= not bool(self.first_sets[symbol])
            if not epsilon_in_symbol_firsts:
                break
        if epsilon_in_symbol_firsts:
            sequence_firsts.add('')
        return sequence_firsts
    
    def __eq__(self, other: 'Rule') -> bool:
        """
        Проверяет на равенство правила self и other
        :param other: правило для сравнения
        :return: True, если правила одинаковы по нетерминалу и содержанию, иначе False
        """
        return self.nonterminal == other.nonterminal and self.rule_tuple == other.rule_tuple
    
    def __hash__(self) -> int:
        """
        Хэш-функция опирается только на терминал и правило
        Запрещается объединение правил разных синтаксисов и разных словарей множеств FIRST
        :return: значение хэш-функции в виде целого числа
        """
        return hash((self.nonterminal, self.rule_tuple))
    
    def __repr__(self) -> str:
        """Возвращает строковое представление объекта"""
        n = "''"
        return f"Rule({self.nonterminal} -> {' '.join(x if x else n for x in self.rule_tuple)})"


class AbstractItem:
    """
    Абстрактный класс "Продукция состояния"
    """
    
    def __init__(self, rule: Rule, dot_index: int):
        """
        Конструктор
        :param rule: правило
        :param dot_index: индекс текущей позиции в правиле
        """
        if type(self) is AbstractItem:
            raise NotImplementedError(f'{type(self).__name__} is an abstract class and cannot be instantiated directly')
        self.rule = rule  # ссылка
        self.dot_index = dot_index
        self.lookaheads = set()
        self.generate_item = AbstractItem  # атрибут должен быть переопределен в дочернем классе
    
    def new_items_from_symbol_after_dot(self) -> Set['AbstractItem']:
        """
        Возвращает новые элементы по текущей позиции (в правиле)
        :return: множество новых элементов по текущей позиции (в правиле)
        """
        r = set()
        if self.dot_index < len(self.rule.rule_tuple):
            nonterminal = self.rule.rule_tuple[self.dot_index]
            for rule in self.rule.syntax_bnf.get(nonterminal, []):
                self.generate_item(rule=Rule(self.rule.syntax_bnf, self.rule.first_sets, nonterminal, rule),
                                   dot_index=0).add_unique_to(r)
        return r
    
    def new_item_after_shift(self) -> 'AbstractItem':
        """
        Возвращает новый элемент после текущей позиции в правиле
        :return: новый элемент после текущей позиции в правиле
        """
        if self.dot_index < len(self.rule.rule_tuple) and self.rule.rule_tuple[self.dot_index] != '':
            return self.generate_item(rule=self.rule, dot_index=self.dot_index + 1)
    
    def add_unique_to(self, items: Set['AbstractItem']) -> bool:
        """
        Добавляет текущий элемент в множество items
        :param items: множество элементов
        :return: True, если элемент был добавлен в items, иначе False
        """
        previous_length = len(items)
        items.add(self)
        return previous_length != len(items)
    
    def __eq__(self, other: 'AbstractItem') -> bool:
        """
        Проверяет на равенство элементы self и other
        :param other: элемент для сравнения
        :return: True, если элементы одинаковы по правилу и текущей позиции, иначе False
        """
        return self.rule == other.rule and self.dot_index == other.dot_index
    
    def __hash__(self) -> int:
        """
        Хэш-функция опирается только на правило и текущую позицию
        Запрещается объединение элементов с одинаковыми правилами и позициями, но с разными lookaheads
        :return: значение хэш-функции в виде целого числа
        """
        return hash((self.rule, self.dot_index))
    
    def __repr__(self) -> str:
        """Возвращает строковое представление объекта"""
        return f"{type(self).__name__}(rule={self.rule}, dot={self.dot_index}, lookaheads={self.lookaheads})"


class LR1Item(AbstractItem):
    """
    Класс "LR(1) продукция состояния"
    """
    
    def __init__(self, rule: Rule, dot_index: int):
        """
        Конструктор
        :param rule: правило
        :param dot_index: индекс текущей позиции в правиле
        """
        super().__init__(rule, dot_index)
        self.generate_item = LR1Item
        if self.dot_index == 0:
            self.lookaheads.add('$')
    
    def new_items_from_symbol_after_dot(self) -> Set['AbstractItem']:  # overrides method in AbstractItem
        """
        Возвращает новые элементы по текущей позиции (в правиле)
        :return: множество новых элементов по текущей позиции (в правиле)
        """
        r = super().new_items_from_symbol_after_dot()
        if not r:
            return r
        new_lookaheads = set()
        firsts_after_symbol_after_dot = self.rule.get_sequence_firsts(self.rule.rule_tuple[self.dot_index + 1:])
        if '' in firsts_after_symbol_after_dot:
            firsts_after_symbol_after_dot.remove('')
            new_lookaheads.update(self.lookaheads)
        new_lookaheads.update(firsts_after_symbol_after_dot)
        for x in r:
            x.lookaheads = new_lookaheads.copy()
        return r
    
    def new_item_after_shift(self) -> 'AbstractItem':  # overrides method in AbstractItem
        """
        Возвращает новый элемент после текущей позиции в правиле
        :return: новый элемент после текущей позиции в правиле
        """
        r = super().new_item_after_shift()
        if r is not None:
            r.lookaheads = self.lookaheads.copy()
        return r
    
    def add_unique_to(self, items: Set['AbstractItem']) -> bool:  # overrides method in AbstractItem
        """
        Добавляет текущий элемент в список/множество items
        :param items: список/множество элементов
        :return: True, если элемент был добавлен в items, иначе False
        """
        for item in items:
            if super().__eq__(item):
                previous_length = len(item.lookaheads)
                item.lookaheads.update(self.lookaheads)
                return previous_length != len(item.lookaheads)
        items.add(self)
        return True
    
    def __eq__(self, other: 'LR1Item') -> bool:  # overrides method in AbstractItem
        """
        Проверяет на равенство элементы self и other
        :param other: элемент для сравнения
        :return: True, если элементы одинаковы по правилу и текущей позиции, иначе False
        """
        return self.rule == other.rule and self.dot_index == other.dot_index and self.lookaheads == other.lookaheads
    
    def __hash__(self) -> int:  # overrides method in AbstractItem
        """
        Хэш-функция опирается только на правило и текущую позицию
        Запрещается объединение элементов с одинаковыми правилами и позициями, но с разными lookaheads
        :return: значение хэш-функции в виде целого числа
        """
        return hash((self.rule, self.dot_index, tuple(sorted(self.lookaheads))))


class LALR1Item(LR1Item):
    """
    Класс "LALR(1) продукция состояния"
    """
    
    def __init__(self, rule: Rule, dot_index: int):
        """
        Конструктор
        :param rule: правило
        :param dot_index: индекс текущей позиции в правиле
        """
        super().__init__(rule, dot_index)
        self.generate_item = LALR1Item
    
    def __eq__(self, other: 'LALR1Item') -> bool:  # overrides method in LR1Item
        return AbstractItem.__eq__(self, other)
    
    def __hash__(self) -> int:  # overrides method in LR1Item
        return AbstractItem.__hash__(self)


class State:
    """
    Класс "Состояние"
    """
    
    def __init__(self, index: int, items: Set[AbstractItem]):
        """
        Конструктор
        :param index: индекс состояния
        :param items: продукции состояния
        """
        self.index = index
        self.items = items  # ссылка
        self.closure = items.copy()  # копия
        self.gotos = dict()
        self.keys = set()
    
    def __eq__(self, other: 'State') -> bool:
        """
        Проверяет на равенство состояния self и other
        :param other: состояние для сравнения
        :return: True, если состояния одинаковы по продукциям состояния, иначе False
        """
        return self.items == other.items
    
    def __repr__(self, width=80) -> str:
        """Возвращает строковое представление объекта"""
        items = pprint.pformat(self.items, width=width)
        closure = pprint.pformat(self.closure, width=width)
        gotos = pprint.pformat(self.gotos, width=width)
        return f"{type(self).__name__}(index={self.index}, keys={self.keys}\n" \
               f"items={items}\nclosure={closure}\ngotos={gotos})"


def construct_first_sets(syntax_bnf: Dict[str, List[Tuple[str]]]) -> Dict[str, Set[str]]:
    """
    Конструирует словарь множеств FIRST
    :param syntax_bnf: синтаксис языка в обычной (не расширенной) форме Бэкуса-Наура
    :return: словарь множеств FIRST
    """
    first_sets = {k: set() for k in syntax_bnf}
    changed = True
    while changed:
        changed = False
        for nonterminal, rules in syntax_bnf.items():
            for rule in rules:
                for symbol in rule:
                    if symbol in syntax_bnf:
                        have_epsilon = False
                        for first_terminal in first_sets[symbol]:
                            have_epsilon |= (first_terminal == '')
                            if first_terminal not in first_sets[nonterminal]:
                                first_sets[nonterminal].add(first_terminal)
                                changed = True
                        if not have_epsilon:
                            break
                    else:
                        if symbol not in first_sets[nonterminal]:
                            first_sets[nonterminal].add(symbol)
                            changed = True
                        if symbol != '':
                            break
    return first_sets


def construct_lr_closure_table(initial_nonterminal: str,
                               syntax_bnf: Dict[str, List[Tuple[str]]],
                               first_sets: Dict[str, Set[str]],
                               lalr: bool) -> List[State]:
    """
    Конструирует таблицу LR closure
    :param initial_nonterminal: начальный нетерминал
    :param syntax_bnf: синтаксис языка в обычной (не расширенной) форме Бэкуса-Наура
    :param first_sets: словарь множеств FIRST
    :param lalr: использовать GLR(1) [False] или GLALR(1) [True] парсер;
                 в случае GLR: число состояний, затрат по времени и памяти будет больше,
                               но при этом бОльшая распознавательная способность
                 в случае GLALR: число состояний, затрат по времени и памяти будет меньше,
                               но при этом меньшая распознавательная способность
                 большинство реально используемых языков программирования имеют GLALR(1)-грамматики
    :return: таблица LR closure
    """
    generate_item = LALR1Item if lalr else LR1Item
    lr_closure_table = [State(index=0, items={generate_item(rule=Rule(
        syntax_bnf, first_sets, initial_nonterminal, syntax_bnf[initial_nonterminal][0]
    ), dot_index=0)})]
    i = 0
    while i < len(lr_closure_table):
        state = lr_closure_table[i]
        update_closure(state)
        if add_gotos(state, lr_closure_table):
            i = 0
        else:
            i += 1
    return lr_closure_table


def update_closure(state: State) -> None:
    """
    Дополняет closure состояния state
    :param state: состояние как минимум с одним closure, на основе которого происходит дополнение
    """
    hq = collections.deque(state.closure)
    while hq:
        for x in hq.popleft().new_items_from_symbol_after_dot():
            if x.add_unique_to(state.closure):
                hq.append(x)


def add_gotos(state: State, lr_closure_table: List[State]) -> bool:
    """
    Добавляет новые состояния и переходы в соответствии с state
    :param state: состояние с заполненным closure
    :param lr_closure_table: таблица LR closure
    :return: True, если нужно вернуться к первому состоянию в lr_closure_table, иначе False
    """
    lookaheads_propagated = False
    new_states = dict()
    for item in state.closure:
        new_item = item.new_item_after_shift()
        if new_item is not None:
            symbol_after_dot = item.rule.rule_tuple[item.dot_index]
            state.keys.add(symbol_after_dot)
            new_item.add_unique_to(new_states.setdefault(symbol_after_dot, set()))
    for key in state.keys:
        new_state = State(len(lr_closure_table), set(new_states[key]))
        target_state_index = next((i for i, prev_state in enumerate(lr_closure_table)
                                   if prev_state == new_state), -1)
        if target_state_index == -1:
            lr_closure_table.append(new_state)
            target_state_index = new_state.index
        else:
            for item in new_state.items:
                lookaheads_propagated |= item.add_unique_to(lr_closure_table[target_state_index].items)
        state.gotos.setdefault(key, set()).add(target_state_index)
    return lookaheads_propagated


def construct_lr_table(lr_closure_table: List[State]) -> Dict[Tuple[int, str], Set[Union[int, Rule]]]:
    """
    Конструирует таблицу LR на основе таблицы LR closure
    :param lr_closure_table: таблица LR closure
    :return: таблица LR
    """
    n_states = []
    lr_table = dict()
    for state in lr_closure_table:
        n_state = len(n_states)
        n_states.append(n_state)
        for key in state.keys:
            lr_table[(n_state, key)] = state.gotos[key]
        for item in state.closure:
            if item.dot_index == len(item.rule.rule_tuple) or item.rule.rule_tuple[0] == '':
                for lookahead in item.lookaheads:
                    lr_table.setdefault((n_state, lookahead), set()).add(item.rule)
    return lr_table

Соседние файлы в папке Programs