Скачиваний:
0
Добавлен:
15.04.2026
Размер:
56.89 Кб
Скачать
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 = {'!': {'': '<logical-not>', '=': '<not-equals>'},
        #        '"': ('<string>', C90Language.string),
        #        '#': ('<preprocessor-directive>', C90Language.preprocessor_directive),
        #        '%': {'': '<modulus>', '=': '<modulus-equals>'},
        #        '&': {'': '<binary-and>', '&': '<logical-and>', '=': '<binary-and-equals>'},
        #        "'": ('<character-constant>', C90Language.character),
        #        '(': '<open-bracket>', ')': '<close-bracket>',
        #        '*': {'': '<multiply-or-pointer>', '=': '<mul-equals>'},
        #        '+': {'': '<plus>', '+': '<increment>', '=': '<plus-equals>'},
        #        ',': '<comma>',
        #        '-': {'': '<minus>',
        #              '-': '<decrement>',
        #              '=': '<minus-equals>',
        #              '>': '<pointer-access>'},
        #        '.': {'': '<dot>', '.': {'.': '<ellipsis>'}},
        #        '/': {'': '<divide>',
        #              '*': ('<multi-line-comment>', C90Language.multi_line_comment),
        #              '/': ('<single-line-comment>', C90Language.single_line_comment),
        #              '=': '<div-equals>'},
        #        ':': '<colon>', ';': '<semicolon>',
        #        '<': {'': '<less-than>', '<': '<left-shift>', '=': '<less-than-or-equal-to>'},
        #        '=': {'': '<assignment>', '=': '<equal-to>'},
        #        '>': {'': '<greater-than>',
        #              '=': '<greater-than-or-equal-to>',
        #              '>': '<right-shift>'},
        #        'L': {'"': ('<string>', C90Language.string),
        #              "'": ('<character-constant>', C90Language.character)},
        #        '[': '<open-square-bracket>', ']': '<close-square-bracket>',
        #        '^': {'': '<binary-xor>', '=': '<binary-xor-equals>'},
        #        '{': '<open-curly-bracket>', '}': '<close-curly-bracket>',
        #        '|': {'': '<binary-or>', '=': '<binary-or-equals>', '|': '<logical-or>'},
        #        '~': '<tilde>'}
        # 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)
        # fsm['L'] = {'"': ('<string>', C90Language.string),
        #             "'": ('<character-constant>', C90Language.character),
        #             '': fsm['L']}
        # return fsm
        
        # ----- автоматическое задание конечного автомата
        # (результат работы аналогичен закомментированному ручному заданию конечного автомата)
        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>')

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