diff --git a/AGENTS.md b/AGENTS.md index e4d21ab..5581f1a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,8 +11,7 @@ Разбор строк — только посимвольно, через циклы и сравнения. Это делает код переносимым на другие языки без изменений логики. 2. **Минимум ООП.** - - Существующие классы (`Pass1`, `Pass2`, `Parser`) **не переписывать** без явной команды. Они работают — не трогай. - - Новые классы **не создавать**. Код пишется процедурно: функции принимают данные, возвращают данные. + - Классы **не создавать**. Код пишется процедурно: функции принимают данные, возвращают данные. 3. **Изоляция логики важнее DRY.** Если модуль может быть автономным — он должен быть автономным. Допустимо дублирование кода ради независимости модуля от других. Пример: кодировщики фиксированного и переменного размера содержат копии функций, чтобы не импортировать их и оставаться полностью самостоятельными. diff --git a/квс_парсер.py b/квс_парсер.py index 9cede9a..04d49a7 100644 --- a/квс_парсер.py +++ b/квс_парсер.py @@ -57,255 +57,295 @@ def read_tokens(input_file): return tokens -class Parser: - def __init__(self, tokens): - self.tokens = tokens - self.pos = 0 - self.current_section = ".text" - self.parsed_lines = [] - - def peek(self): - if self.pos < len(self.tokens): - return self.tokens[self.pos] +# ==================== СОСТОЯНИЕ ПАРСЕРА ==================== +# Хранится в словаре, передаётся во все функции явно + +def parser_init(tokens): + """Создаёт начальное состояние парсера""" + return { + 'tokens': tokens, + 'pos': 0, + 'current_section': '.text', + 'parsed_lines': [] + } + + +def parser_peek(state): + """Подсмотреть текущий токен""" + if state['pos'] < len(state['tokens']): + return state['tokens'][state['pos']] + return None + + +def parser_next_token(state): + """Взять текущий токен и продвинуться""" + tok = parser_peek(state) + if tok: + state['pos'] += 1 + return tok + + +def parser_expect(state, expected_type): + """Ожидать токен определённого типа""" + tok = parser_next_token(state) + if tok is None or tok[0] != expected_type: + raise ValueError(f"Ожидался {expected_type}, получено {tok}") + return tok + + +def parser_expect_one_of(state, expected_types): + """Ожидает один из нескольких типов токенов""" + tok = parser_peek(state) + if tok is None or tok[0] not in expected_types: + raise ValueError(f"Ожидался один из {expected_types}, получено {tok}") + return parser_next_token(state) + + +# ==================== РАЗБОР ОПЕРАНДОВ ==================== + +def parse_operand(state): + """Разбирает один операнд""" + tok = parser_peek(state) + if tok is None: return None - - def next_token(self): - tok = self.peek() - if tok: - self.pos += 1 - return tok - - def expect(self, expected_type): - tok = self.next_token() - if tok is None or tok[0] != expected_type: - raise ValueError(f"Ожидался {expected_type}, получено {tok}") - return tok - - def expect_one_of(self, expected_types): - """Ожидает один из нескольких типов токенов""" - tok = self.peek() - if tok is None or tok[0] not in expected_types: - raise ValueError(f"Ожидался один из {expected_types}, получено {tok}") - return self.next_token() - - def parse_line(self): - tok = self.peek() + + if tok[0] == 'СК_ОТКР': + parser_next_token(state) + return parse_memory_operand(state) + elif tok[0] == 'ЧИСЛО': + return parser_next_token(state)[1] + elif tok[0] == 'СТРОКА': + return parser_next_token(state)[1] + elif tok[0] == 'СЛОВО': + return parser_next_token(state)[1] + else: + raise ValueError(f"Неожиданный токен в операнде: {tok}") + + +def parse_memory_operand(state): + """Разбирает операнд памяти""" + parts = [] + + while True: + tok = parser_peek(state) + if tok is None: + raise ValueError("Незакрытая квадратная скобка") + + if tok[0] == 'СК_ЗАКР': + parser_next_token(state) + break + + if tok[0] in ('СЛОВО', 'ЧИСЛО', 'ПЛЮС', 'МИНУС', 'ЗВЕЗДА'): + parts.append(parser_next_token(state)[1]) + else: + raise ValueError(f"Неожиданный токен в адресации: {tok}") + + if len(parts) == 1: + part = parts[0] + if part in REGISTERS: + return make_memory_operand('register_indirect', base_reg=part) + + expr = ' '.join(parts) + return f"[{expr}]" + + +# ==================== РАЗБОР ДИРЕКТИВ ==================== + +def parse_directive(state, word): + """ + Разбор директив ассемблера. + Вызывает ошибку при некорректном синтаксисе или контексте. + """ + # === Секции === + if word == '.текст': + state['current_section'] = ".text" + state['parsed_lines'].append(f"DIRECTIVE:{word}") + + elif word == '.данные': + state['current_section'] = ".data" + state['parsed_lines'].append(f"DIRECTIVE:{word}") + + elif word == '.бнд': + state['current_section'] = ".бнд" + state['parsed_lines'].append(f"DIRECTIVE:{word}") + + # === Глобальные метки === + elif word == '.глобал': + label = parser_expect(state, 'СЛОВО')[1] + state['parsed_lines'].append(f"DIRECTIVE:{word}:{label}") + + # === Строковые директивы (запрещены в .бнд) === + elif word in ('.строка_нуль', '.строка'): + if state['current_section'] == ".бнд": + raise ValueError( + "Ошибка: секция .бнд не поддерживает инициализированные данные " + "(используйте .резб, .резс, .рездс, .резкс)" + ) + string_tok = parser_expect(state, 'СТРОКА') + state['parsed_lines'].append(f"DIRECTIVE:{word}:{state['current_section']}:{string_tok[1]}") + + # === Константы (запрещены в .бнд) === + elif word == '.константа': + if state['current_section'] == ".бнд": + raise ValueError( + "Ошибка: секция .бнд не поддерживает инициализированные данные " + "(используйте .резб, .резс, .рездс, .резкс)" + ) + name = parser_expect(state, 'СЛОВО')[1] + eq_tok = parser_peek(state) + if eq_tok and eq_tok[0] == 'СЛОВО' and eq_tok[1] == '=': + parser_next_token(state) + value_tok = parser_expect_one_of(state, ['СЛОВО', 'ЧИСЛО']) + state['parsed_lines'].append(f"DIRECTIVE:{word}:{name}:{value_tok[1]}") + + # === Байты (запрещены в .бнд) === + elif word == '.байт': + if state['current_section'] == ".бнд": + raise ValueError( + "Ошибка: секция .бнд не поддерживает инициализированные данные " + "(используйте .резб, .резс, .рездс, .резкс)" + ) + bytes_list = [] + while True: + tok = parser_peek(state) + if tok is None or tok[0] == 'НОВСТР' or tok[0] == 'ЗАПЯТАЯ': + if tok and tok[0] == 'ЗАПЯТАЯ': + parser_next_token(state) + else: + break + else: + val_tok = parser_expect_one_of(state, ['СЛОВО', 'ЧИСЛО', 'СТРОКА']) + bytes_list.append(val_tok[1]) + state['parsed_lines'].append(f"DIRECTIVE:{word}:{state['current_section']}:" + ",".join(bytes_list)) + + # === Директивы резервирования (только в .бнд) === + elif word in ('.резб', '.резс', '.рездс', '.резкс'): + if state['current_section'] != ".бнд": + raise ValueError( + f"Ошибка: директива {word} допустима только внутри секции .бнд" + ) + count_tok = parser_expect(state, 'ЧИСЛО') + count = int(count_tok[1]) + if count <= 0: + raise ValueError( + f"Ошибка: размер резервирования должен быть положительным целым числом" + ) + state['parsed_lines'].append( + f"DIRECTIVE:{word}:{state['current_section']}:{count}" + ) + + else: + raise ValueError(f"Неизвестная директива: {word}") + + +# ==================== РАЗБОР ИНСТРУКЦИЙ ==================== + +def parse_instruction(state, mnemonic): + """Разбор инструкции с операндами""" + if mnemonic not in INSTRUCTIONS: + raise ValueError(f"Неизвестная инструкция: {mnemonic}") + + operands = [] + while True: + tok = parser_peek(state) + if tok is None or tok[0] == 'НОВСТР': + break + if tok[0] == 'ЗАПЯТАЯ': + parser_next_token(state) + continue + operands.append(parse_operand(state)) + + str_operands = [] + for op in operands: + if isinstance(op, tuple) and len(op) >= 2 and op[0] in ('absolute', 'register_indirect', 'complex'): + str_operands.append(memory_operand_to_ast_string(op)) + else: + str_operands.append(op) + + state['parsed_lines'].append(f"INSTR:{mnemonic}:{state['current_section']}:" + ",".join(str_operands)) + + +# ==================== РАЗБОР ИНСТРУКЦИИ ИЛИ ДИРЕКТИВЫ ==================== + +def parse_instruction_or_directive(state): + """Определяет, инструкция или директива, и вызывает нужный разбор""" + tok = parser_peek(state) + if tok is None or tok[0] == 'НОВСТР': + return True + + if tok[0] != 'СЛОВО': + raise ValueError(f"Ожидалось слово, получено: {tok}") + + word = parser_next_token(state)[1] + + if word.startswith('.'): + parse_directive(state, word) + else: + parse_instruction(state, word) + + return True + + +# ==================== РАЗБОР СТРОКИ ==================== + +def parse_line(state): + """Разбор одной строки (может содержать метку)""" + tok = parser_peek(state) + if tok is None: + return False + + # Пропускаем маркер конца строки + if tok[0] == 'НОВСТР': + parser_next_token(state) + tok = parser_peek(state) if tok is None: return False - - # Пропускаем маркер конца строки - if tok[0] == 'НОВСТР': - self.next_token() - tok = self.peek() - if tok is None: - return False - - # Проверяем, есть ли метка (СЛОВО за которым следует ДВОЕТОЧИЕ) - if tok[0] == 'СЛОВО': - if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1][0] == 'ДВОЕТОЧИЕ': - label_name = self.next_token()[1] - self.next_token() # ДВОЕТОЧИЕ - self.parsed_lines.append(f"LABEL:{label_name}:{self.current_section}") - - next_tok = self.peek() - if next_tok and next_tok[0] != 'НОВСТР': - return self.parse_instruction_or_directive() - return True - - return self.parse_instruction_or_directive() - - def parse_instruction_or_directive(self): - tok = self.peek() - if tok is None or tok[0] == 'НОВСТР': + + # Проверяем, есть ли метка (СЛОВО за которым следует ДВОЕТОЧИЕ) + if tok[0] == 'СЛОВО': + if state['pos'] + 1 < len(state['tokens']) and state['tokens'][state['pos'] + 1][0] == 'ДВОЕТОЧИЕ': + label_name = parser_next_token(state)[1] + parser_next_token(state) # ДВОЕТОЧИЕ + state['parsed_lines'].append(f"LABEL:{label_name}:{state['current_section']}") + + next_tok = parser_peek(state) + if next_tok and next_tok[0] != 'НОВСТР': + return parse_instruction_or_directive(state) return True - - if tok[0] != 'СЛОВО': - raise ValueError(f"Ожидалось слово, получено: {tok}") - - word = self.next_token()[1] - - if word.startswith('.'): - self.parse_directive(word) - else: - self.parse_instruction(word) - - return True - - def parse_directive(self, word): - """ - Разбор директив ассемблера. - Вызывает ошибку при некорректном синтаксисе или контексте. - """ - # === Секции === - if word == '.текст': - self.current_section = ".text" - self.parsed_lines.append(f"DIRECTIVE:{word}") - elif word == '.данные': - self.current_section = ".data" - self.parsed_lines.append(f"DIRECTIVE:{word}") + return parse_instruction_or_directive(state) - elif word == '.бнд': - self.current_section = ".бнд" - self.parsed_lines.append(f"DIRECTIVE:{word}") - # === Глобальные метки === - elif word == '.глобал': - label = self.expect('СЛОВО')[1] - self.parsed_lines.append(f"DIRECTIVE:{word}:{label}") +# ==================== ГЛАВНЫЙ ЦИКЛ ==================== - # === Строковые директивы (запрещены в .бнд) === - elif word in ('.строка_нуль', '.строка'): - if self.current_section == ".бнд": - raise ValueError( - "Ошибка: секция .бнд не поддерживает инициализированные данные " - "(используйте .резб, .резс, .рездс, .резкс)" - ) - string_tok = self.expect('СТРОКА') - self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:{string_tok[1]}") +def parse_all(tokens): + """Разбирает все токены, возвращает список строк AST""" + state = parser_init(tokens) + while state['pos'] < len(state['tokens']): + parse_line(state) + return state['parsed_lines'] - # === Константы (запрещены в .бнд) === - elif word == '.константа': - if self.current_section == ".бнд": - raise ValueError( - "Ошибка: секция .бнд не поддерживает инициализированные данные " - "(используйте .резб, .резс, .рездс, .резкс)" - ) - name = self.expect('СЛОВО')[1] - eq_tok = self.peek() - if eq_tok and eq_tok[0] == 'СЛОВО' and eq_tok[1] == '=': - self.next_token() - value_tok = self.expect_one_of(['СЛОВО', 'ЧИСЛО']) - self.parsed_lines.append(f"DIRECTIVE:{word}:{name}:{value_tok[1]}") - - # === Байты (запрещены в .бнд) === - elif word == '.байт': - if self.current_section == ".бнд": - raise ValueError( - "Ошибка: секция .бнд не поддерживает инициализированные данные " - "(используйте .резб, .резс, .рездс, .резкс)" - ) - bytes_list = [] - while True: - tok = self.peek() - if tok is None or tok[0] == 'НОВСТР' or tok[0] == 'ЗАПЯТАЯ': - if tok and tok[0] == 'ЗАПЯТАЯ': - self.next_token() - else: - break - else: - val_tok = self.expect_one_of(['СЛОВО', 'ЧИСЛО', 'СТРОКА']) - bytes_list.append(val_tok[1]) - self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:" + ",".join(bytes_list)) - - # === Директивы резервирования (только в .бнд) === - elif word in ('.резб', '.резс', '.рездс', '.резкс'): - if self.current_section != ".бнд": - raise ValueError( - f"Ошибка: директива {word} допустима только внутри секции .бнд" - ) - count_tok = self.expect('ЧИСЛО') - count = int(count_tok[1]) - if count <= 0: - raise ValueError( - f"Ошибка: размер резервирования должен быть положительным целым числом" - ) - self.parsed_lines.append( - f"DIRECTIVE:{word}:{self.current_section}:{count}" - ) - - else: - raise ValueError(f"Неизвестная директива: {word}") - - def parse_operand(self): - """Разбирает один операнд""" - tok = self.peek() - if tok is None: - return None - - if tok[0] == 'СК_ОТКР': - self.next_token() - return self.parse_memory_operand() - elif tok[0] == 'ЧИСЛО': - return self.next_token()[1] - elif tok[0] == 'СТРОКА': - return self.next_token()[1] - elif tok[0] == 'СЛОВО': - return self.next_token()[1] - else: - raise ValueError(f"Неожиданный токен в операнде: {tok}") - - def parse_memory_operand(self): - """Разбирает операнд памяти""" - parts = [] - - while True: - tok = self.peek() - if tok is None: - raise ValueError("Незакрытая квадратная скобка") - - if tok[0] == 'СК_ЗАКР': - self.next_token() - break - - if tok[0] in ('СЛОВО', 'ЧИСЛО', 'ПЛЮС', 'МИНУС', 'ЗВЕЗДА'): - parts.append(self.next_token()[1]) - else: - raise ValueError(f"Неожиданный токен в адресации: {tok}") - - if len(parts) == 1: - part = parts[0] - if part in REGISTERS: - return make_memory_operand('register_indirect', base_reg=part) - - expr = ' '.join(parts) - return f"[{expr}]" - - def parse_instruction(self, mnemonic): - if mnemonic not in INSTRUCTIONS: - raise ValueError(f"Неизвестная инструкция: {mnemonic}") - - operands = [] - while True: - tok = self.peek() - if tok is None or tok[0] == 'НОВСТР': - break - if tok[0] == 'ЗАПЯТАЯ': - self.next_token() - continue - operands.append(self.parse_operand()) - - str_operands = [] - for op in operands: - if isinstance(op, tuple) and len(op) >= 2 and op[0] in ('absolute', 'register_indirect', 'complex'): - str_operands.append(memory_operand_to_ast_string(op)) - else: - str_operands.append(op) - - self.parsed_lines.append(f"INSTR:{mnemonic}:{self.current_section}:" + ",".join(str_operands)) - - def parse_all(self): - while self.pos < len(self.tokens): - self.parse_line() - return self.parsed_lines +# ==================== ЗАПИСЬ РЕЗУЛЬТАТА ==================== def write_ast(ast_lines, output_file): + """Записывает строки AST в файл""" with open(output_file, 'w', encoding='utf-8') as f: for line in ast_lines: f.write(line + '\n') +# ==================== ТОЧКА ВХОДА ==================== + if __name__ == "__main__": if len(sys.argv) != 3: print("Использование: python kvs_parser.py <вход.токены> <выход.аст>") sys.exit(1) - + input_file = sys.argv[1] output_file = sys.argv[2] - + tokens = read_tokens(input_file) - parser = Parser(tokens) - ast_lines = parser.parse_all() + ast_lines = parse_all(tokens) write_ast(ast_lines, output_file) print(f"Парсер: {len(ast_lines)} строк AST записано в {output_file}") \ No newline at end of file diff --git a/тесты/черновик/черновик.квс b/тесты/черновик/черновик.квс index 9f81e1c..bc6581c 100644 --- a/тесты/черновик/черновик.квс +++ b/тесты/черновик/черновик.квс @@ -1,22 +1 @@ -; тесты/вычитание/вычитание.квс -; Тест вычитания: 50 - 8 = 42 -.текст -.глобал _start - -_start: - ; Загружаем числа - переместить_имм раикс, 50 - переместить_имм рбикс, 12 - - ; Вычитаем: раикс = раикс - рбикс - вычесть раикс, рбикс - - ; Результат (42) в RDI для exit - переместить рдиай, раикс - - ; Выход - переместить_имм раикс, 60 - вызов_системы - -.данные -_пусто: .байт 0 \ No newline at end of file +вызвать и вернуться уже реализованы, но не тестировались \ No newline at end of file