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