kvs/квс_парсер.py

311 lines
12 KiB
Python
Raw Normal View History

2026-05-02 23:04:46 +03:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
2026-06-20 04:16:50 +03:00
Парсер КВС (адаптирован под русские токены)
Принимает: файл токенов от лексера (русские названия типов)
2026-05-02 23:04:46 +03:00
Выдаёт: текстовый файл с AST-структурой
Поддерживает сложную адресацию:
- [регистр + регистр*масштаб + смещение]
2026-05-02 23:04:46 +03:00
"""
import sys
sys.path.insert(0, '.')
from kvs_data import REGISTERS, INSTRUCTIONS
2026-05-17 15:18:35 +03:00
def make_memory_operand(addr_type, base_reg=None, displacement=None, label=None):
"""Создаёт кортеж операнда памяти.
Формат: (addr_type, base_reg, displacement, label)
"""
return (addr_type, base_reg, displacement, label)
def memory_operand_to_ast_string(mem_op):
"""Преобразует кортеж памяти в строку AST.
Формат: 'MEM:тип:данные'
"""
addr_type = mem_op[0]
if addr_type == 'absolute':
if mem_op[3] is not None: # label
return f"MEM:absolute:{mem_op[3]}"
else: # displacement
return f"MEM:absolute:{mem_op[2]}"
elif addr_type == 'register_indirect':
return f"MEM:reg_indirect:{mem_op[1]}"
elif addr_type == 'complex':
return f"MEM:complex:{mem_op[1]}+{mem_op[2]}*{mem_op[3]}+{mem_op[4]}"
else:
return f"MEM:unknown"
2026-05-02 23:04:46 +03:00
def read_tokens(input_file):
2026-06-20 04:16:50 +03:00
"""Читает токены из файла (русские названия типов)"""
2026-05-02 23:04:46 +03:00
tokens = []
with open(input_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
colon_pos = line.find(':')
if colon_pos == -1:
continue
tok_type = line[:colon_pos]
tok_value = line[colon_pos + 1:]
tokens.append((tok_type, tok_value))
return tokens
2026-05-17 15:18:35 +03:00
2026-05-02 23:04:46 +03:00
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]
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()
2026-05-02 23:04:46 +03:00
def parse_line(self):
tok = self.peek()
if tok is None:
return False
2026-06-20 04:16:50 +03:00
# Пропускаем маркер конца строки
if tok[0] == 'НОВСТР':
2026-05-02 23:04:46 +03:00
self.next_token()
tok = self.peek()
if tok is None:
return False
2026-06-20 04:16:50 +03:00
# Проверяем, есть ли метка (СЛОВО за которым следует ДВОЕТОЧИЕ)
if tok[0] == 'СЛОВО':
if self.pos + 1 < len(self.tokens) and self.tokens[self.pos + 1][0] == 'ДВОЕТОЧИЕ':
2026-05-02 23:04:46 +03:00
label_name = self.next_token()[1]
2026-06-20 04:16:50 +03:00
self.next_token() # ДВОЕТОЧИЕ
2026-05-02 23:04:46 +03:00
self.parsed_lines.append(f"LABEL:{label_name}:{self.current_section}")
next_tok = self.peek()
2026-06-20 04:16:50 +03:00
if next_tok and next_tok[0] != 'НОВСТР':
2026-05-02 23:04:46 +03:00
return self.parse_instruction_or_directive()
return True
return self.parse_instruction_or_directive()
def parse_instruction_or_directive(self):
tok = self.peek()
2026-06-20 04:16:50 +03:00
if tok is None or tok[0] == 'НОВСТР':
2026-05-02 23:04:46 +03:00
return True
2026-06-20 04:16:50 +03:00
if tok[0] != 'СЛОВО':
2026-05-02 23:04:46 +03:00
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):
2026-05-17 11:22:39 +03:00
"""
Разбор директив ассемблера.
Вызывает ошибку при некорректном синтаксисе или контексте.
"""
# === Секции ===
2026-05-02 23:04:46 +03:00
if word == '.текст':
self.current_section = ".text"
self.parsed_lines.append(f"DIRECTIVE:{word}")
2026-05-17 11:22:39 +03:00
2026-05-02 23:04:46 +03:00
elif word == '.данные':
self.current_section = ".data"
self.parsed_lines.append(f"DIRECTIVE:{word}")
2026-05-17 11:22:39 +03:00
elif word == '.бнд':
self.current_section = ".бнд"
self.parsed_lines.append(f"DIRECTIVE:{word}")
# === Глобальные метки ===
2026-05-02 23:04:46 +03:00
elif word == '.глобал':
2026-06-20 04:16:50 +03:00
label = self.expect('СЛОВО')[1]
2026-05-02 23:04:46 +03:00
self.parsed_lines.append(f"DIRECTIVE:{word}:{label}")
2026-05-17 11:22:39 +03:00
# === Строковые директивы (запрещены в .бнд) ===
2026-05-02 23:04:46 +03:00
elif word in ('.строка_нуль', '.строка'):
2026-05-17 11:22:39 +03:00
if self.current_section == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
2026-06-20 04:16:50 +03:00
string_tok = self.expect('СТРОКА')
2026-05-02 23:04:46 +03:00
self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:{string_tok[1]}")
2026-05-17 11:22:39 +03:00
# === Константы (запрещены в .бнд) ===
2026-05-02 23:04:46 +03:00
elif word == '.константа':
2026-05-17 11:22:39 +03:00
if self.current_section == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
2026-06-20 04:16:50 +03:00
name = self.expect('СЛОВО')[1]
2026-05-02 23:04:46 +03:00
eq_tok = self.peek()
2026-06-20 04:16:50 +03:00
if eq_tok and eq_tok[0] == 'СЛОВО' and eq_tok[1] == '=':
2026-05-02 23:04:46 +03:00
self.next_token()
2026-06-20 04:16:50 +03:00
value_tok = self.expect_one_of(['СЛОВО', 'ЧИСЛО'])
2026-05-02 23:04:46 +03:00
self.parsed_lines.append(f"DIRECTIVE:{word}:{name}:{value_tok[1]}")
2026-05-17 11:22:39 +03:00
# === Байты (запрещены в .бнд) ===
2026-05-02 23:04:46 +03:00
elif word == '.байт':
2026-05-17 11:22:39 +03:00
if self.current_section == ".бнд":
raise ValueError(
"Ошибка: секция .бнд не поддерживает инициализированные данные "
"(используйте .резб, .резс, .рездс, .резкс)"
)
2026-05-02 23:04:46 +03:00
bytes_list = []
while True:
tok = self.peek()
2026-06-20 04:16:50 +03:00
if tok is None or tok[0] == 'НОВСТР' or tok[0] == 'ЗАПЯТАЯ':
if tok and tok[0] == 'ЗАПЯТАЯ':
2026-05-02 23:04:46 +03:00
self.next_token()
else:
break
else:
2026-06-20 04:16:50 +03:00
val_tok = self.expect_one_of(['СЛОВО', 'ЧИСЛО', 'СТРОКА'])
bytes_list.append(val_tok[1])
2026-05-02 23:04:46 +03:00
self.parsed_lines.append(f"DIRECTIVE:{word}:{self.current_section}:" + ",".join(bytes_list))
2026-05-17 11:22:39 +03:00
# === Директивы резервирования (только в .бнд) ===
elif word in ('.резб', '.резс', '.рездс', '.резкс'):
if self.current_section != ".бнд":
raise ValueError(
f"Ошибка: директива {word} допустима только внутри секции .бнд"
)
2026-06-20 04:16:50 +03:00
count_tok = self.expect('ЧИСЛО')
2026-05-17 11:22:39 +03:00
count = int(count_tok[1])
if count <= 0:
raise ValueError(
f"Ошибка: размер резервирования должен быть положительным целым числом"
)
self.parsed_lines.append(
f"DIRECTIVE:{word}:{self.current_section}:{count}"
)
2026-05-02 23:04:46 +03:00
else:
raise ValueError(f"Неизвестная директива: {word}")
def parse_operand(self):
2026-06-20 04:16:50 +03:00
"""Разбирает один операнд"""
tok = self.peek()
if tok is None:
return None
2026-06-20 04:16:50 +03:00
if tok[0] == 'СК_ОТКР':
self.next_token()
return self.parse_memory_operand()
2026-06-20 04:16:50 +03:00
elif tok[0] == 'ЧИСЛО':
return self.next_token()[1]
2026-06-20 04:16:50 +03:00
elif tok[0] == 'СТРОКА':
return self.next_token()[1]
2026-06-20 04:16:50 +03:00
elif tok[0] == 'СЛОВО':
return self.next_token()[1]
else:
raise ValueError(f"Неожиданный токен в операнде: {tok}")
def parse_memory_operand(self):
2026-06-20 04:16:50 +03:00
"""Разбирает операнд памяти"""
parts = []
while True:
tok = self.peek()
if tok is None:
raise ValueError("Незакрытая квадратная скобка")
2026-06-20 04:16:50 +03:00
if tok[0] == 'СК_ЗАКР':
self.next_token()
break
2026-06-20 04:16:50 +03:00
if tok[0] in ('СЛОВО', 'ЧИСЛО', 'ПЛЮС', 'МИНУС', 'ЗВЕЗДА'):
parts.append(self.next_token()[1])
else:
raise ValueError(f"Неожиданный токен в адресации: {tok}")
2026-05-17 15:18:35 +03:00
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}]"
2026-05-02 23:04:46 +03:00
def parse_instruction(self, mnemonic):
if mnemonic not in INSTRUCTIONS:
raise ValueError(f"Неизвестная инструкция: {mnemonic}")
operands = []
while True:
tok = self.peek()
2026-06-20 04:16:50 +03:00
if tok is None or tok[0] == 'НОВСТР':
2026-05-02 23:04:46 +03:00
break
2026-06-20 04:16:50 +03:00
if tok[0] == 'ЗАПЯТАЯ':
2026-05-02 23:04:46 +03:00
self.next_token()
continue
operands.append(self.parse_operand())
2026-05-02 23:04:46 +03:00
2026-05-17 15:18:35 +03:00
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))
2026-05-02 23:04:46 +03:00
def parse_all(self):
while self.pos < len(self.tokens):
self.parse_line()
return self.parsed_lines
2026-05-17 15:18:35 +03:00
2026-05-02 23:04:46 +03:00
def write_ast(ast_lines, output_file):
with open(output_file, 'w', encoding='utf-8') as f:
for line in ast_lines:
f.write(line + '\n')
2026-05-17 15:18:35 +03:00
2026-05-02 23:04:46 +03:00
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()
write_ast(ast_lines, output_file)
print(f"Парсер: {len(ast_lines)} строк AST записано в {output_file}")