Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТАФЯ. Лабораторная работа 6.docx
Скачиваний:
0
Добавлен:
15.04.2026
Размер:
287.67 Кб
Скачать

МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ,

СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ

Федеральное государственное бюджетное образовательное учреждение высшего образования «Санкт-Петербургский государственный университет телекоммуникаций им. проф. М. А. Бонч-Бруевича»

(СПбГУТ)

Факультет инфокоммуникационных сетей и систем

Кафедра программной инженерии и вычислительной техники

Лабораторная работа №6

по дисциплине «Теория алгоритмов и формальных языков»

студенты гр. ИКПИ-93

_______________

Смирнов Д.А.

Тюришев М.А.

Цыганков М.А.

Козлов Н.С.

ассистент каф. ПИиВТ

_______________

Марочкина А. В.

Санкт-Петербург

2022

Постановка задачи

Создать упрощенный вариант синтаксического анализатора кода на произвольном языке программирования. (Выбран язык C90.)

Ход работы

Код разработанной программы на языке Python приведен в таблицах 1-2.

Таблица 1. Код разработанной программы (1)

main.py

import collections import re import string import sys import textwrap import traceback

from typing import Any, Union, Tuple, List, Set, Dict, Pattern, Deque

import syntax_lib

class C90Language:

"""Класс языка C90: лексика и синтаксис"""

# ========================================== Для лексического анализа

# поля, описывающие шаблоны лексем токенов в виде регулярных выражений multi_line_comment = re.compile(r'/\*.*?\*/', re.S) single_line_comment = re.compile(r'//.*')

integer = re.compile(r"[+-]?(0[0-7]+|0X[0-9A-F]+|\d+)U?L{,2}(?![0-9A-Z.])", re.IGNORECASE)

float = re.compile(r"[+-]?(\d+(\.\d*)?(E[+-]?\d+)?|\.\d+(E[+-]?\d+)?)[FL]?(?![0-9A-Z.])", re.IGNORECASE) space = re.compile(r"\s+") character = re.compile(

r"L?\'(\\(['\"?\\abfnrtv]|[0-7]{,3}|x[0-9a-fA-F]{,2}|u[0-9a-fA-F]{,4}|U[0-9a-fA-F]{,8})|[^\\'])\'"

)

string = re.compile(r'L?\".*?\"')

identifier = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')

preprocessor_directive = re.compile(r'#.*') # обычно обрабатываются препроцессором до лексического анализа # будем пропускать директивы препроцессора

keywords = { # множество ключевых слов языка

'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum',

'extern', 'float', 'for', 'goto', 'if', 'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof',

'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while'

}

@staticmethod

def get_lexical_fsm() -> Dict[str, Union[str, dict,

Tuple[str, Pattern],

List[Tuple[str, Pattern]]]]:

"""Возвращает лексику языка в виде конечного автомата"""

# ----- ручное задание конечного автомата

fsm = {

':': '<colon>', ';': '<semicolon>', ',': '<comma>', '~': '<tilde>',

'(': '<open-bracket>', ')': '<close-bracket>',

'[': '<open-square-bracket>', ']': '<close-square-bracket>',

'{': '<open-curly-bracket>', '}': '<close-curly-bracket>',

'>>': '<right-shift>', '>=': '<greater-than-or-equal-to>', '>': '<greater-than>',

'<<': '<left-shift>', '<=': '<less-than-or-equal-to>', '<': '<less-than>',

'--': '<decrement>', '-=': '<minus-equals>', '->': '<pointer-access>', '-': '<minus>',

'++': '<increment>', '+=': '<plus-equals>', '+': '<plus>',

'.': '<dot>', '...': '<ellipsis>',

'&&': '<logical-and>', '&=': '<binary-and-equals>', '&': '<binary-and>',

'||': '<logical-or>', '|=': '<binary-or-equals>', '|': '<binary-or>',

'^=': '<binary-xor-equals>', '^': '<binary-xor>',

'*=': '<mul-equals>', '*': '<multiply-or-pointer>',

'/*': ('<multi-line-comment>', C90Language.multi_line_comment),

'//': ('<single-line-comment>', C90Language.single_line_comment),

'/=': '<div-equals>', '/': '<divide>',

'%=': '<modulus-equals>', '%': '<modulus>', '==': '<equal-to>', '=': '<assignment>',

'!=': '<not-equals>', '!': '<logical-not>',

'#': ('<preprocessor-directive>', C90Language.preprocessor_directive),

"'": ('<character-constant>', C90Language.character),

'"': ('<string>', C90Language.string),

"L'": ('<character-constant>', C90Language.character),

'L"': ('<string>', C90Language.string)

} for digit in string.digits:

fsm[digit] = [('<integer-constant>', C90Language.integer), ('<floating-constant>', C90Language.float)] for letter in string.ascii_letters + '_':

fsm[letter] = ('<identifier>', C90Language.identifier) for space in string.whitespace:

fsm[space] = ('<whitespace>', C90Language.space) # ----- алгоритм преобразования структуры в конечный автомат

# -1- удаляем вложенность

q = collections.deque([('', fsm)]) # очередь: (lexeme, token) fsm = dict() # новый конечный автомат while q: # пока очередь не пуста

lexeme, token = q.popleft() # получаем из очереди очередной узел if isinstance(token, dict): # если token -- это промежуточный подузел

for lex, tok in token.items(): # проходим по каждому подузлу промежуточного подузла

q.append((lexeme + lex, tok)) # добавляем в очередь, пример: ('-' + '=', '<minus-equals>') else: # если token -- это конечный подузел

if fsm.get(lexeme, token) != token: # если уже есть запись по fsm[lexeme], не равная token raise ULexicalFSMError(f'Invalid: {fsm[lexeme]} != {token} [{fsm}]') fsm[lexeme] = token # если записи по fsm[lexeme] нет или есть, но равна token

# -2- формируем вложенность

for lexeme, token in list(fsm.items()): # проходим по каждому подузлу lexeme_len = len(lexeme) # длина лексемы (например, len('-=') -> 2) if lexeme_len > 1: # если длина лексемы больше 1 current = fsm # current указывает на текущий узел i = 0 # индекс текущего символа в lexeme while i < lexeme_len: # пока индекс < длины лексемы ch = lexeme[i] # получаем i символ

if i == lexeme_len - 1: # если i == последнему индексу # если уже есть запись по current[ch], не равная token if current.get(ch, token) != token: raise ULexicalFSMError(f'Invalid: {current[ch]} != {token} [{current}]') current[ch] = token # если записи по current[ch] нет или есть, но равна token

elif ch in current: # если i < последний индекс и символ лексемы есть в current if isinstance(current[ch], dict): # если current[ch] -- это промежуточный узел current = current[ch] # переходим в него else: # иначе

i += 1 # переходим к следующему символу

if i == lexeme_len - 1: # если символов не осталось, записываем как str/tuple current[ch] = {lexeme[i]: token, '': current[ch]} else: # иначе записываем как dict и переходим в него d = dict()

current[ch] = {lexeme[i]: d, '': current[ch]} current = d

else: # если i < последний индекс и символ лексемы отсутствует в current if i >= lexeme_len - 1: # если символов не осталось, записываем как str/tuple current[ch] = token

else: # иначе записываем как dict и переходим в него d = dict() current[ch] = d current = d

i += 1 # переходим к следующему символу

fsm.pop(lexeme) # удаляем соответствие lexeme (длина > 1) -> token return fsm

@staticmethod def get_keywords() -> Set[str]:

"""Возвращает множество ключевых слов языка""" return set(C90Language.keywords)

# ========================================== Для синтаксического анализа

skipped_tokens = {'<multi-line-comment>', '<whitespace>', # множество пропускаемых токенов '<single-line-comment>', '<preprocessor-directive>'}

@staticmethod

def get_syntax_ebnf() -> (str, Dict[str, Union[str,

Set[Tuple[str, str]],

Tuple[Union[str, Set[Tuple[str, str]]]],

List[Union[str,

Set[Tuple[str, str]],

Tuple[Union[str, Set[Tuple[str, str]]]]]]]]):

"""

Возвращает начальный нетерминал и синтаксис языка в виде расширенной формы Бэкуса-Наура: str -- обязательное вхождение (ровно 1 раз)

{(str, ???)} -- повторение str:

{(str, '?')} -- 0 или 1 раз

{(str, '+')} -- от 1 раза и более

{(str, '*')} -- от 0 раз и более

(e1, e2, ..., en) -- конкатенация элементов (ei -- либо обязательное вхождение, либо повторение)

[v1, v2, ..., vn] -- выбор одного из n вариантов (vi -- либо обязательное вхождение, либо повторение, либо конкатенация)

Синтаксис взят из приложения A13 в книге

"Kernighan, Brian; Ritchie, Dennis M. (March 1988).

The C Programming Language (2nd ed.). Englewood Cliffs, NJ: Prentice Hall. ISBN 0-13-110362-8."

"""

initial_nonterminal = '<translation-unit>' syntax_ebnf = {

'<translation-unit>': {('<external-declaration>', '+')},

'<external-declaration>': ['<function-definition>', '<declaration>'],

'<function-definition>': ({('<declaration-specifier>', '*')}, '<declarator>',

{('<declaration>', '*')}, '<compound-statement>'),

'<declaration>': ({('<declaration-specifier>', '+')}, {('<init-declarator-list>', '?')}, ';'),

'<declaration-specifier>': ['<storage-class-specifier>', '<type-specifier>', '<type-qualifier>'],

'<storage-class-specifier>': ['auto', 'register', 'static', 'extern', 'typedef'],

'<type-specifier>': ['void', 'char', 'short', 'int', 'long', 'float', 'double', 'signed', 'unsigned',

'<struct-or-union-specifier>', '<enum-specifier>', '<typedef-name>'],

'<typedef-name>': '<identifier>',

'<type-qualifier>': ['const', 'volatile'],

'<struct-or-union-specifier>': [

('<struct-or-union>', {('<identifier>', '?')}, '{', {('<struct-declaration>', '+')}, '}'), ('<struct-or-union>', '<identifier>')

],

'<struct-or-union>': ['struct', 'union'],

'<init-declarator-list>': ['<init-declarator>',

('<init-declarator-list>', ',', '<init-declarator>')], '<init-declarator>': ['<declarator>',

('<declarator>', '=', '<initializer>')],

'<struct-declaration>': ({('<specifier-qualifier>', '+')}, '<struct-declarator-list>', ';'),

'<specifier-qualifier>': ['<type-specifier>', '<type-qualifier>'],

'<struct-declarator-list>': ['<struct-declarator>',

('<struct-declarator-list>', ',', '<struct-declarator>')],

'<struct-declarator>': ['<declarator>',

({('<declarator>', '?')}, ':', '<constant-expression>')],

'<enum-specifier>': [('enum', {('<identifier>', '?')}, '{', '<enumerator-list>', '}'),

('enum', '<identifier>')],

'<enumerator-list>': ['<enumerator>',

('<enumerator-list>', ',', '<enumerator>')],

'<enumerator>': ['<identifier>',

('<identifier>', '=', '<constant-expression>')],

'<declarator>': ({('<pointer>', '?')}, '<direct-declarator>'),

'<direct-declarator>': ['<identifier>',

('(', '<declarator>', ')'),

('<direct-declarator>', '[', {('<constant-expression>', '?')}, ']'),

('<direct-declarator>', '(', '<parameter-type-list>', ')'),

('<direct-declarator>', '(', {('<identifier>', '*')}, ')')],

'<pointer>': ('*', {('<type-qualifier>', '*')}, {('<pointer>', '?')}),

'<parameter-type-list>': ['<parameter-list>',

('<parameter-list>', ',', '...')],

'<parameter-list>': ['<parameter-declaration>',

('<parameter-list>', ',', '<parameter-declaration>')],

'<parameter-declaration>': [

({('<declaration-specifier>', '+')}, '<declarator>'),

({('<declaration-specifier>', '+')}, {('<abstract-declarator>', '?')}), ],

'<initializer>': ['<assignment-expression>',

('{', '<initializer-list>', {(',', '?')}, '}')],

'<initializer-list>': ['<initializer>',

('<initializer-list>', ',', '<initializer>')],

'<type-name>': ({('<specifier-qualifier>', '+')}, {('<abstract-declarator>', '?')}),

'<abstract-declarator>': ['<pointer>',

({('<pointer>', '?')}, '<direct-abstract-declarator>')],

'<direct-abstract-declarator>': [('(', '<abstract-declarator>', ')'),

({('<direct-abstract-declarator>', '?')},

'[', {('<constant-expression>', '?')}, ']'),

({('<direct-abstract-declarator>', '?')},

'(', {('<parameter-type-list>', '?')}, ')')],

'<statement>': ['<labeled-statement>', '<expression-statement>', '<compound-statement>',

'<selection-statement>', '<iteration-statement>', '<jump-statement>'],

'<labeled-statement>': [('<identifier>', ':', '<statement>'),

('case', '<constant-expression>', ':', '<statement>'),

('default', ':', '<statement>')],

'<expression-statement>': ({('<expression>', '?')}, ';'),

'<compound-statement>': ('{', {('<declaration>', '*')}, {('<statement>', '*')}, '}'),

'<selection-statement>': [('if', '(', '<expression>', ')', '<statement>'),

('if', '(', '<expression>', ')', '<statement>', 'else', '<statement>'),

('switch', '(', '<expression>', ')', '<statement>')],

'<iteration-statement>': [('while', '(', '<expression>', ')', '<statement>'),

('do', '<statement>', 'while', '(', '<expression>', ')', ';'),

('for', '(', {('<expression>', '?')}, ';', {('<expression>', '?')}, ';',

{('<expression>', '?')}, ')', '<statement>')],

'<jump-statement>': [('goto', '<identifier>', ';'),

('continue', ';'),

('break', ';'),

('return', {('<expression>', '?')}, ';')],

'<expression>': ['<assignment-expression>',

('<expression>', ',', '<assignment-expression>')],

'<assignment-expression>': ['<conditional-expression>',

('<unary-expression>', '<assignment-operator>', '<assignment-expression>')],

'<assignment-operator>': ['=', '*=', '/=', '%=', '+=', '-=', '<<=', '>>=', '&=', '^=', '|='],

'<conditional-expression>': ['<logical-or-expression>',

('<logical-or-expression>',

'?', '<expression>',

':', '<conditional-expression>')],

'<constant-expression>': '<conditional-expression>',

'<logical-or-expression>': ['<logical-and-expression>',

('<logical-or-expression>', '||', '<logical-and-expression>')],

'<logical-and-expression>': ['<inclusive-or-expression>',

('<logical-and-expression>', '&&', '<inclusive-or-expression>')],

'<inclusive-or-expression>': ['<exclusive-or-expression>',

('<inclusive-or-expression>', '|', '<exclusive-or-expression>')], '<exclusive-or-expression>': ['<and-expression>',

('<exclusive-or-expression>', '^', '<and-expression>')],

'<and-expression>': ['<equality-expression>',

('<and-expression>', '&', '<equality-expression>')],

'<equality-expression>': ['<relational-expression>',

('<equality-expression>', '==', '<relational-expression>'),

('<equality-expression>', '!=', '<relational-expression>')],

'<relational-expression>': ['<shift-expression>',

('<relational-expression>', '<', '<shift-expression>'),

('<relational-expression>', '>', '<shift-expression>'),

('<relational-expression>', '<=', '<shift-expression>'),

('<relational-expression>', '>=', '<shift-expression>')],

'<shift-expression>': ['<additive-expression>',

('<shift-expression>', '<<', '<additive-expression>'),

('<shift-expression>', '>>', '<additive-expression>')],

'<additive-expression>': ['<multiplicative-expression>',

('<additive-expression>', '+', '<multiplicative-expression>'),

('<additive-expression>', '-', '<multiplicative-expression>')],

'<multiplicative-expression>': ['<cast-expression>',

('<multiplicative-expression>', '*', '<cast-expression>'),

('<multiplicative-expression>', '/', '<cast-expression>'),

('<multiplicative-expression>', '%', '<cast-expression>')],

'<cast-expression>': ['<unary-expression>', ('(', '<type-name>', ')', '<cast-expression>')],

'<unary-expression>': ['<postfix-expression>',

('++', '<unary-expression>'),

('--', '<unary-expression>'),

('<unary-operator>', '<cast-expression>'),

('sizeof', '<unary-expression>'),

('sizeof', '(', '<type-name>', ')')],

'<unary-operator>': ['&', '*', '+', '-', '~', '!'],

'<postfix-expression>': ['<primary-expression>',

('<postfix-expression>', '[', '<expression>', ']'),

('<postfix-expression>', '(', '<expression>', ')'),

('<postfix-expression>', '.', '<identifier>'),

('<postfix-expression>', '->', '<identifier>'),

('<postfix-expression>', '++'),

('<postfix-expression>', '--')],

'<primary-expression>': ['<identifier>', '<constant>', '<string>', ('(', '<expression>', ')')], '<constant>': ['<integer-constant>', '<character-constant>', '<floating-constant>'],

}

return initial_nonterminal, syntax_ebnf

class ULexicalError(ValueError):

"""Исключение возникает из-за лексической ошибки"""

def __init__(self, **kwargs): self.params = {k: repr(v) if isinstance(v, str) else v for k, v in kwargs.items() if isinstance(v, (int, float)) or v} super().__init__()

def __str__(self): return '[' + ' | '.join(f"{name}: {p}" for name, p in self.params.items()) + ']'

class ULexicalFSMError(TypeError):

"""Исключение возникает из-за ошибки в описании лексики языка в виде конечного автомата""" pass # не требует реализации; функциональность = TypeError

def lexical_analyzer(lexical_fsm: Dict[str, Union[str, dict,

Tuple[str, Pattern], List[Tuple[str, Pattern]]]], keywords: Union[List[str], Set[str]], code: str) -> List[Tuple[str, str]]:

"""

Лексер анализирует код согласно лексике языка и возвращает список токенов и лексем, либо выбрасывает исключение

:param lexical_fsm: лексика языка в виде конечного автомата

:param keywords: список/множество ключевых слов языка

:param code: код для лексического анализа

:return: список токенов и лексем; например, [..., ('<identifier>', 'Animal'), ...] """

# проверка lexical_fsm -- лексики языка в виде конечного автомата q = collections.deque(lexical_fsm.items()) # очередь для проверки while q: tuple_item = q.popleft()

if not isinstance(tuple_item, tuple): # tuple ключ:значение

raise ULexicalFSMError(f"Invalid type: {tuple_item} [{type(tuple_item)}] (must be tuple)") key, value = tuple_item

if not isinstance(key, str): # ключ должен быть строкой

raise ULexicalFSMError(f"Invalid type: {repr(key)} [{type(key)}] (must be str)") if len(key) > 1: # длина ключа должна быть <= 1

raise ULexicalFSMError(f"Length of string must be <= 1, got {repr(key)} with len = {len(key)}") if not isinstance(value, (str, tuple, list, dict)): # значение может быть только str / tuple / list / dict raise ULexicalFSMError(f"Invalid type: {value} [{type(value)}] (must be str / tuple / list / dict)") if isinstance(value, (tuple, list)): # если значение tuple / list tuples = [value] if isinstance(value, tuple) else value # создаем list if len(tuples) == 0: # длина списка должна быть > 0

raise ULexicalFSMError(f"Number of values in list must be >= 1, got: {tuples}") for tuple_item in tuples: # для каждого кортежа в списке if len(tuple_item) != 2: # длина кортежа должна быть == 2

raise ULexicalFSMError(f'Number of values in tuple must be 2, got: {tuple_item}') key, value = tuple_item

if not isinstance(key, str): # ключ должен быть str

raise ULexicalFSMError(f"Invalid type: {repr(key)} [{type(key)}] (must be str)") if not isinstance(value, Pattern): # значение должно быть Pattern [re.compile(...)] raise ULexicalFSMError(f"Invalid type: {repr(value)} [{type(value)}] (must be Pattern)") elif isinstance(value, dict): # в случае словаря -- добавляем его элементы в очередь q.extend(value.items())

# лексический анализ

tokens_and_lexemes = [] # список токенов и лексем current_state = lexical_fsm # текущее состояние

p, i, n = 0, 0, len(code) # предыдущий индекс, текущий индекс и длина code token, lexeme = '', '' # токен и лексема line = 1 # текущий номер строки кода while i < n: # пока текущий индекс < длины code

r = current_state.get(code[i]) # переходим к следующему состоянию конечного автомата if r is None: # если следующего состояния нет, то смотрим состояние "по умолчанию" r = current_state.get('') # пробуем перейти в него

i -= 1 # был считан один лишний символ (по индексу i), поэтому отнимаем 1

if r is None: # если нужной ветки нет, то получена неверная лексема i += 1 # увеличиваем на 1, чтобы получить индекс неверной лексемы prev_n = code[:i].rfind('\n') + 1 # начало строки next_n = code[i:].find('\n') + i # конец строки

raise ULexicalError(code_startswith=code[prev_n if prev_n != -1 else i:next_n if next_n != -1 else i + 1], symbol=code[i], line=line) if isinstance(r, str): # если символ-атом i += 1 # переход к следующему символу token = r

lexeme = repr(code[p:i])[1:-1] p = i # обновляем предыдущий индекс

line += code[p:i].count('\n') # обновляем номер текущей строки elif isinstance(r, (tuple, list)): # если регулярное выражение if not isinstance(r, list): r = [r]

for attempt, t in enumerate(r, 1): # r заведомо не пустой token, token_regexp = t

match = re.match(token_regexp, code[p:]) # поиск по регулярному выражению if not match and attempt == len(r): # если неудачный поиск на последней попытке pos = code[p:].find('\n')

startswith = code[p:(p + pos + 1 if pos != -1 else None)]

raise ULexicalError(code_startswith=startswith, line=line, regexp=token_regexp) elif not match: # если неудачный поиск на текущей попытке, то переход к следующей continue

match_len = match.span(0)[1] # если поиск удачный, получаем число найденных символов i = p + match_len # обновляем текущую позицию lexeme = code[p:i]

p = i # обновляем предыдущий индекс

if lexeme in keywords: # если лексема есть в множестве ключевых слов языка token = lexeme

line += lexeme.count('\n') # обновляем номер текущей строки break

else: # если несколько веток (dict)

i += 1 # один символ был считан, поэтому добавляем 1 current_state = r # переходим к следующему состоянию continue

tokens_and_lexemes.append((token, lexeme)) # добавляем токен и лексему current_state = lexical_fsm # текущее состояние -- начальное состояние return tokens_and_lexemes

class USyntaxError(ValueError):

"""Исключение возникает из-за синтаксической ошибки"""

def __init__(self, **kwargs): self.params = {k: repr(v) if isinstance(v, str) else v for k, v in kwargs.items() if isinstance(v, (int, float)) or v} super().__init__()

def __str__(self): return '[' + ' | '.join(f"{name}: {p}" for name, p in self.params.items()) + ']'

class USyntaxEBNFError(TypeError):

"""Исключение возникает из-за ошибки в описании синтаксиса языка в виде расширенной формы Бэкуса-Наура""" pass # не требует реализации; функциональность = TypeError

def syntax_analyzer(initial_nonterminal: str, syntax_ebnf: Dict[str, Union[str,

Set[Tuple[str, str]],

Tuple[Union[str, Set[Tuple[str, str]]]],

List[Union[str,

Set[Tuple[str, str]],

Tuple[Union[str, Set[Tuple[str, str]]]]]]]], tokens_and_lexemes: List[Tuple[str, str]],

skipped_tokens: Set[str], lalr: bool) -> List[Tuple[str, str, str, str]]:

"""

Синтаксический анализатор генерирует GLR(1) или GLALR(1) парсер по синтаксису языка в расширенной форме Бэкуса-Наура и анализирует токены согласно сгенерированному парсеру

:param initial_nonterminal: начальный нетерминал

:param syntax_ebnf: синтаксис языка в расширенной форме Бэкуса-Наура

:param tokens_and_lexemes: список токенов и лексем; например, [..., ('<identifier>', 'Animal'), ...] :param skipped_tokens: множество пропускаемых токенов

:param lalr: использовать GLR(1) [False] или GLALR(1) [True] парсер;

в случае GLR: число состояний, затрат по времени и памяти будет больше,

но при этом бОльшая распознавательная способность + подсказки по ошибкам синтаксиса в случае GLALR: число состояний, затрат по времени и памяти будет меньше,

но при этом меньшая распознавательная способность и наличие некорректных подсказок

(большинство реально используемых языков программирования имеют GLALR(1)-грамматики) :return: история синтаксического анализа

"""

# проверка syntax_ebnf -- синтаксиса языка в расширенной форме Бэкуса-Наура # а также преобразование его в синтаксис языка в обычной форме

def gen_rules(s: str, new_s: str, times: str) -> List[Tuple[str]]: """Генерирует правила для нового состояния-повторения""" rules = []

if times != '+': # times == '*' или times == '?' rules.append(('',))

if times != '*': # times == '+' или times == '?' rules.append((s,))

if times != '?': # times == '*' или times == '+' rules.append((new_s, s)) return rules

if initial_nonterminal not in syntax_ebnf: # если начального нетерминала нет в синтаксисе raise USyntaxEBNFError(f'Initial nonterminal {repr(initial_nonterminal)} missing in syntax') q = collections.deque([(initial_nonterminal, syntax_ebnf[initial_nonterminal])]) # очередь new_initial_nonterminal = initial_nonterminal + "'" # новый начальный нетерминал while new_initial_nonterminal in syntax_ebnf: # должен иметь новое уникальное название new_initial_nonterminal += "'"

syntax_bnf = {new_initial_nonterminal: [(initial_nonterminal,)]} # синтаксис в виде обычной формы Бэкуса-Наура seen = set() # множество уже обработанных нетерминалов while q: nonterminal, rules = q.popleft() # достаем первый нетерминал из очереди if not isinstance(nonterminal, str): # нетерминал должен быть строкой

raise USyntaxEBNFError(f'Invalid type: {nonterminal} [{type(nonterminal)}] (must be str)') if not isinstance(rules, (str, set, tuple, list)): # правило может быть только str / set / tuple / list raise USyntaxEBNFError(f'Invalid type: {rules} [{type(rules)}] (must be str / set / tuple / list)') if not isinstance(rules, list): # если одно правило, то преобразование в список rules = [rules]

new_rules = list() # список новых правил

for rule in rules: # для каждого правила из старого списка правил

if not isinstance(rule, (str, set, tuple)): # правило должно быть только str / set / tuple raise USyntaxEBNFError(f'Invalid type: {rule} [{type(rule)}] (must be str / set / tuple)') elif not isinstance(rule, tuple): # если не конкатенация, то преобразование в конкатенацию rule = (rule,)

new_rule = [] # новое правило для добавления в список новых правил for subrule in rule: # для каждой части правила

if not isinstance(subrule, (str, set)): # часть правила должна быть str / set raise USyntaxEBNFError(f'Invalid type: {subrule} [{type(subrule)}] (must be str / set)') if isinstance(subrule, set): # если часть правила -- повторение if len(subrule) != 1: # длина set должна быть == 1

raise USyntaxEBNFError(f'Number of tuples in set must be == 1, got: {subrule}') str_tuple = next(iter(subrule))

# set должен содержать один tuple с 2 строками if not isinstance(str_tuple, tuple) or len(str_tuple) != 2 \ or not all(isinstance(s, str) for s in str_tuple): raise USyntaxEBNFError(f'Set must contains one tuple with two strings, got: {str_tuple}') s, times = str_tuple if not s: # если s пуст (epsilon)

raise USyntaxEBNFError(f'An empty character is forbidden to repeat, got: {rule}') if times not in '?+*': raise USyntaxEBNFError(f'Second value must be in "?+*", got: {times}') new_s = s + times # новое название для нетерминала-повторения while new_s in syntax_bnf and syntax_bnf[new_s] != gen_rules(s, new_s, times): new_s += times

syntax_bnf[new_s] = gen_rules(s, new_s, times) # правила для нового нетерминала

new_rule.append(new_s) # добавление нетерминала в новое правило

if s in syntax_ebnf and s not in seen: # если s есть в синтаксисе и еще не был обработан

q.append((s, syntax_ebnf[s])) # добавляем в очередь seen.add(s) # добавляем в множество уже обработанных нетерминалов elif subrule or len(rule) == 1: # если непустая часть правила или epsilon-правило new_rule.append(subrule) # добавляем часть правила # если subrule есть в синтаксисе и еще не был обработан if subrule in syntax_ebnf and subrule not in seen:

q.append((subrule, syntax_ebnf[subrule])) # добавляем в очередь seen.add(subrule) # добавляем в множество уже обработанных нетерминалов new_rules.append(tuple(new_rule)) # добавляем новое правило в список syntax_bnf[nonterminal] = new_rules # сопоставляем нетерминалу список правил initial_nonterminal = new_initial_nonterminal # заменяем начальный нетерминал if '$' in skipped_tokens: raise USyntaxEBNFError(f'Dollar symbol must not be in skipped tokens, got: {skipped_tokens}')

# синтаксический анализ

def to_readable_format(xs: List[Any]):

"""Преобразует входной список xs в строку, разделенную пробелами""" return ' '.join(repr(x) for x in xs) if xs else "''"

def format_state_and_token(state_and_token: Tuple[str, str]):

"""Форматирует текущее состояние и токен для записи истории синтаксического анализа""" return f'state={repr(state_and_token[0])}, token={repr(state_and_token[1])}'

def update_error(state: int, error_row: int, error_col: int, description: str):

"""Регистрирует последнюю ошибку"""

nonlocal last_error_row, last_error_col, last_error_string if error_row > last_error_row or error_row == last_error_row and error_col >= last_error_col: last_error_row = error_row last_error_col = error_col expected_lexemes = set()

for n, lookahead in lr_table.keys():

if n == state: if lookahead in syntax_bnf: expected_lexemes.update(first_sets[lookahead]) else: expected_lexemes.add(lookahead) last_error_string = f'{description}.' * bool(description)

last_error_string += f' Expected any of: {expected_lexemes}' * bool(expected_lexemes)

# конструируем словарь множеств first_sets

first_sets = syntax_lib.construct_first_sets(syntax_bnf)

# конструируем таблицу LR closure

lr_closure_table = syntax_lib.construct_lr_closure_table(initial_nonterminal, syntax_bnf, first_sets, lalr=lalr)

# конструируем таблицу LR на основе таблицы LR closure lr_table = syntax_lib.construct_lr_table(lr_closure_table)

# определение типа синтаксиса: неоднозначный (Ambiguous) / однозначный (Unambiguous) syntax_rules = len(lr_table.keys()) # число всех правил

ambiguous_syntax_rules = sum(len(v) > 1 for k, v in lr_table.items()) # число неоднозначных правил

main_history = [] # история синтаксического анализа if ambiguous_syntax_rules: main_history.append(('Ambiguous syntax ' +

f'({ambiguous_syntax_rules} ambiguous rules / {syntax_rules} rules)',

'', '', '')) else: main_history.append((f'Unambiguous syntax ({syntax_rules} rules)', '', '', ''))

# проверка последовательности токенов на соответствие синтаксису языка (с механизмом разветвления стеков) stacks: Deque[Tuple[Any, Any, int, int, int]] = collections.deque([(main_history, [0], 0, 1, 1)]) # очередь стеков

status = False # статус проверки

last_error_row = 0 # последняя позиция ошибки (строка) last_error_col = 0 # последняя позиция ошибки (столбец) last_error_string = '' # описание последней ошибки while stacks: # пока есть стеки

history, stack, token_index, error_row, error_col = stacks.popleft() # достаем первый стек из очереди if not stack: # если стек пуст, регистрируем ошибку (такая ошибка маловероятна) update_error(-1, error_row, error_col, 'Parse stack is empty') continue

# если все токены обработаны if token_index >= len(tokens_and_lexemes): token, lexeme = ('$', '') # последний токен и пустая лексема state_and_token = (stack[-1], token)

if state_and_token not in lr_table: # если завершения нет

update_error(stack[-1], error_row, error_col, f'Unexpected end') # регистрация ошибки continue

elif any(r.nonterminal == initial_nonterminal for r in lr_table[state_and_token] if isinstance(r, syntax_lib.Rule)): # если есть завершение status = True # анализ завершен main_history = history

break # выход из цикла с успешным анализом

# если завершение требует reduce else: # если не все токены обработаны

token, lexeme = tokens_and_lexemes[token_index] # получаем очередной токен и лексему state_and_token = (stack[-1], token) # составляем условие перехода new_error_row, new_error_col = error_row, error_col if '\n' in lexeme: # создаем новые текущие позиции new_error_row += lexeme.count('\n')

new_error_col = len(lexeme) - lexeme.rfind('\n') - 1 else:

new_error_col += len(lexeme)

if token in skipped_tokens: # если токен в списке пропускаемых токенов, то пропускаем его

stacks.append((history + [(format_state_and_token(state_and_token),

'skip', '',

to_readable_format(stack))], stack, token_index + 1, new_error_row, new_error_col)) continue

if state_and_token not in lr_table: # если условие перехода не найдено, то state_and_token = (stack[-1], '') # попытка epsilon-правила

if state_and_token not in lr_table: # если и epsilon-правила не было, тогда регистрация ошибки update_error(stack[-1], error_row, error_col, f'Unexpected lexeme {lexeme}') continue

for action in lr_table[state_and_token]: # для каждого возможного перехода if isinstance(action, int): # если новое состояние

new_stack = stack + [token, action] # добавляем токен и состояние в стек stacks.append((history + [(format_state_and_token(state_and_token), f'shift {repr(token)} {action}', '', to_readable_format(new_stack))], new_stack, token_index + 1, new_error_row, new_error_col)) elif isinstance(action, syntax_lib.Rule): # если нужно применить правило nonterminal = action.nonterminal rule = action.rule_tuple

removed = 0 # число последних элементов stack для удаления

stack_pos = len(stack) - 2 # stack[-1] -- int, поэтому от len(stack) - 1 отнимаем 1 rule_pos = len(rule) - 1

while stack_pos >= 1 and rule_pos >= 0: # проверка правила с учетом epsilon-переходов if stack[stack_pos] != rule[rule_pos]: if rule[rule_pos] == '':

rule_pos -= 1

else: break else: removed += 2 rule_pos -= 1 stack_pos -= 2

popped = stack[len(stack) - removed:]

new_stack = stack[:len(stack) - removed] + [nonterminal]

if (new_stack[-2], new_stack[-1]) not in lr_table: # если условие перехода не найдено, то ошибка update_error(new_stack[-2], error_row, error_col, f'Unexpected lexeme {lexeme}') continue

for x in lr_table[(new_stack[-2], new_stack[-1])]: # для каждого возможного перехода next_stack = new_stack + [x] # формируем стек и добавляем его в очередь стеков stacks.append((history +

[(format_state_and_token(state_and_token),

f'reduce {to_readable_format(popped)} -> {repr(next_stack[-2])}', f'push {next_stack[-1]} by ({new_stack[-2]}, {repr(new_stack[-1])})', to_readable_format(next_stack))], next_stack, token_index, error_row, error_col)) if not status: raise USyntaxError(message=f"{last_error_string}",

position=f'{last_error_row}:{last_error_col}') return main_history

def print_history(history):

"""Выводит историю синтаксического анализа"""

def format_function(i, format_strings, s1, s2, s3, s4, wrapper): """Форматирует записи истории синтаксического анализа""" if s2 != 'skip': xs = [x if x == s1 else wrapper.fill(x) for x in (s1, s2, s3, s4) if x] else: xs = [s1 + f', {s2}']

return format_strings[len(xs) - 1].format(i, *xs)

max_n = len(str(len(history)))

wrapper = textwrap.TextWrapper(width=200, initial_indent=' ' * (max_n + 2),

subsequent_indent=' ' * (max_n + 4), break_on_hyphens=False) max_n_s = str(max_n)

format_strings = ['{:>' + max_n_s + '}. {}' + '\n{}' * i for i in range(4)] print(*(format_function(i, format_strings, s1, s2, s3, s4, wrapper) for i, (s1, s2, s3, s4) in enumerate(history)), sep='\n', end='')

def main(code): def print_center(*, message, width, is_start=True, end='\n'): """Печатает текст в центре согласно общей ширине width""" print('=' * width * bool(is_start), message.center(width, '='),

'=' * width * (not bool(is_start)), sep='\n', end=end)

# инициализация

lexical_fsm = C90Language.get_lexical_fsm() # лексика языка в виде конечного автомата keywords = C90Language.get_keywords() # множество ключевых слов языка

initial_nonterminal, syntax_ebnf = C90Language.get_syntax_ebnf() # синтаксис языка в расширенной форме БэкусаНаура

skipped_tokens = C90Language.skipped_tokens # множество пропускаемых токенов

maximum_left_indent = max(map(len, re.findall(r'\'([\w\-<>]+)\'', str(lexical_fsm)))) # макс. длина токена format_function = ('{:>' + str(maximum_left_indent) + '} |\'{}\'|').format # функция для форматирования

# лексический анализ

print_center(message=' Lexical analysis ', width=maximum_left_indent * 2, is_start=True, end='') try:

tokens_and_lexemes = lexical_analyzer(lexical_fsm=lexical_fsm, keywords=keywords, code=code) print(*(format_function(token, repr(lexeme)[1:-1]) for token, lexeme in tokens_and_lexemes), sep='\n', end='')

print_center(message=' Success ', width=maximum_left_indent * 2, is_start=False, end='\n\n') except ULexicalError as e:

print('LexicalError:', e, end='')

print_center(message=' Error ', width=maximum_left_indent * 2, is_start=False, end='\n\n') return except Exception:

print(traceback.format_exc(), end='')

print_center(message=' Error ', width=maximum_left_indent * 2, is_start=False, end='\n\n') return

# преобразование названий некоторых токенов в лексемы: '<plus>' в '+' и др. tokens_and_lexemes = [(token if any(x.isalnum() or x == '_' for x in lexeme) or token in C90Language.skipped_tokens else lexeme, lexeme)

for token, lexeme in tokens_and_lexemes]

# синтаксический анализ (работает только по токенам; лексемы используются в информации об исключении) print_center(message=' Syntax analysis ', width=maximum_left_indent * 2, is_start=True, end='') try: history = syntax_analyzer(initial_nonterminal=initial_nonterminal, syntax_ebnf=syntax_ebnf,

tokens_and_lexemes=tokens_and_lexemes, skipped_tokens=skipped_tokens, lalr=True) print_history(history)

print_center(message=' Success ', width=maximum_left_indent * 2, is_start=False, end='\n\n') except USyntaxError as e: print('SyntaxError:', e, end='')

print_center(message=' Error ', width=maximum_left_indent * 2, is_start=False, end='\n\n') return except Exception: print(traceback.format_exc(), end='')

print_center(message=' Error ', width=maximum_left_indent * 2, is_start=False, end='\n\n') return

if __name__ == "__main__": if len(sys.argv) > 1: try: with open(sys.argv[-1], 'r') as fp: main(fp.read()) except Exception as e: print(*e.args, sep=', ') else: print(f'python3.6 main.py <filename>')

Таблица 2. Код разработанной программы (2)

syntax_lib.py

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

Содержимое файла для синтаксического анализа приведено в таблице 3.

Таблица 3. Содержимое файла для синтаксического анализа

file.c

// Single line comment

/*

Multi line comment block

*/

#include <stdio.h>

#include <stdlib.h> #include <wchar.h>

static enum Enum { VAR_1 = 0, VAR_2, VAR_3 = 0, VAR_4, VAR_5 = 0, VAR_6 } some_enum1, some_enum2; const volatile int global_var = 1 + 1 << sizeof(char), another_var = 2 * global_var; static struct Animal { int animal_int : 32;

const void * volatile animal_reference;

} some_animal1, some_animal2; int main(int _, ...) {

int * const r = (int*) malloc(sizeof(int) * 5), k = (2 >> 1) + (1 << 1) - 1 * 1.0e-5/1e+20; wprintf(L"%d", ((0 <= 0 < 2 > 0 >= 0) == 1 != 0));

((((k += 1) -= 1) *= 1) /= 1 % 2) %= 2;

++k; k++; --k; k--;

((k &= 1 || 1 | 1 & 5 && 5) |= ~k ^ k) ^= !k; do {

for (k = 0; k < 5; ++k) {

wprintf(L"\nHello, world\n");

}

while (++k < 10) continue;

} while (0);

fputwc(L'\n', stdout); free(r); return 0;

}

Вывод программы состоит из 7087 строк. Кратко приведен в таблице 4.

Таблица 4. Вывод программы (кратко)

Вывод программы (кратко)

====================================================

================= Lexical analysis =================

<single-line-comment> |'// Single line comment'|

<whitespace> |'\n'|

<multi-line-comment> |'/*\nMulti line comment block\n*/'|

<whitespace> |'\n'|

<preprocessor-directive> |'#include <stdio.h>'|

<whitespace> |'\n'|

<preprocessor-directive> |'#include <stdlib.h>'|

<whitespace> |'\n'|

<preprocessor-directive> |'#include <wchar.h>'|

<whitespace> |'\n'| static |'static'| <whitespace> |' '| enum |'enum'|

<whitespace> |' '|

<identifier> |'Enum'|

.............................................................................................................. return |'return'|

<whitespace> |' '|

<integer-constant> |'0'|

<semicolon> |';'|

<whitespace> |'\n'|

<close-curly-bracket> |'}'|

===================== Success ====================== ====================================================

====================================================

================= Syntax analysis ==================

  1. Ambiguous syntax (60 ambiguous rules / 6663 rules)

  2. state=0, token='<single-line-comment>', skip

  3. state=0, token='<whitespace>', skip

  4. state=0, token='<multi-line-comment>', skip

  5. state=0, token='<whitespace>', skip

  6. state=0, token='<preprocessor-directive>', skip

  7. state=0, token='<whitespace>', skip

  8. state=0, token='<preprocessor-directive>', skip 8. state=0, token='<whitespace>', skip

  1. state=0, token='<preprocessor-directive>', skip

  2. state=0, token='<whitespace>', skip 11. state=0, token='static' shift 'static' 13

0 'static' 13

12. state=13, token='<whitespace>', skip 13. state=13, token='enum'

reduce 'static' 13 -> '<storage-class-specifier>' push 21 by (0, '<storage-class-specifier>')

0 '<storage-class-specifier>' 21

  1. state=21, token='enum'

reduce '<storage-class-specifier>' 21 -> '<declaration-specifier>' push 22 by (0, '<declaration-specifier>')

    1. '<declaration-specifier>' 22

  1. state=22, token='enum'

reduce '<declaration-specifier>' 22 -> '<declaration-specifier>+' push 4 by (0, '<declaration-specifier>+')

    1. '<declaration-specifier>+' 4

  1. state=4, token='enum' shift 'enum' 17

    1. '<declaration-specifier>+' 4 'enum' 17

  2. state=17, token='<whitespace>', skip 18. state=17, token='<identifier>' shift '<identifier>' 50

    1. '<declaration-specifier>+' 4 'enum' 17 '<identifier>' 50

19. state=50, token='<whitespace>', skip 20. state=50, token='{'

reduce '<identifier>' 50 -> '<identifier>?' push 49 by (17, '<identifier>?')

0 '<declaration-specifier>+' 4 'enum' 17 '<identifier>?' 49

  1. state=49, token='{' shift '{' 60

    1. '<declaration-specifier>+' 4 'enum' 17 '<identifier>?' 49 '{' 60

  2. state=60, token='<whitespace>', skip 23. state=60, token='<identifier>' shift '<identifier>' 112

    1. '<declaration-specifier>+' 4 'enum' 17 '<identifier>?' 49 '{' 60 '<identifier>' 112

24. state=112, token='<whitespace>', skip 25. state=112, token='=' shift '=' 184

0 '<declaration-specifier>+' 4 'enum' 17 '<identifier>?' 49 '{' 60 '<identifier>' 112 '=' 184

..............................................................................................................

  1. state=35, token='$'

reduce '<function-definition>' 35 -> '<external-declaration>' push 44 by (7, '<external-declaration>')

    1. '<external-declaration>+' 7 '<external-declaration>' 44

  1. state=44, token='$'

reduce '<external-declaration>+' 7 '<external-declaration>' 44 -> '<external-declaration>+' push 7 by (0, '<external-declaration>+')

    1. '<external-declaration>+' 7

  1. state=7, token='$'

reduce '<external-declaration>+' 7 -> '<translation-unit>' push 14 by (0, '<translation-unit>')

    1. '<translation-unit>' 14

===================== Success ======================

====================================================

В случае наличия ошибок, синтаксический анализатор выдает сообщение об ошибке (рис. 1-3).

Ожидаемые токены («Expected any of») не всегда содержат действительно ожидаемые токены, т. к. попытка корректно предсказать ожидаемый токен — иногда затратная по времени и памяти задача.

В данном случае генерируется GLALR(1)-парсер. Его преимущество по сравнению с GLR(1)-парсером в том, что число состояний, затрат по времени и памяти используется меньше, однако распознавательная способность ниже. С помощью GLR(1)-парсера корректно предсказать ожидаемые токены проще и его распознавательная способность выше, однако в этом случае число состояний, затрат по времени и памяти будет использоваться больше.

Выбрать генерацию GLR(1)-парсера в коде: аргумент lalr функции syntax_analyzer lalr=False.

Выбрать генерацию GLALR(1)-парсера в коде: аргумент lalr функции syntax_analyzer lalr=True.

Результат синтасического анализа полностью зависит от описания синтаксиса (в коде за синтаксис языка C90 отвечает метод get_syntax_ebnf()).

Приведенное описание синтаксиса языка C в приложении A13 в книге «Kernighan, Brian; Ritchie, Dennis M. (March 1988). The C Programming Language (2nd ed.). Englewood Cliffs, NJ: Prentice Hall. ISBN 0-13-110362-8.» некорректно (рис. 4).

Рисунок 4.Тип не указан, но указан квалификатор const, синтаксический анализ успешен, хотя не должен быть

Все дело в некорректном описании нетерминалов declaration и declaration-specifiers (стр. 234).