351 lines
13 KiB
Python
351 lines
13 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
Парсер КВС (адаптирован под русские токены)
|
||
Принимает: файл токенов от лексера (русские названия типов)
|
||
Выдаёт: текстовый файл с AST-структурой
|
||
|
||
Поддерживает сложную адресацию:
|
||
- [регистр + регистр*масштаб + смещение]
|
||
"""
|
||
|
||
import sys
|
||
sys.path.insert(0, '.')
|
||
from kvs_data import REGISTERS, INSTRUCTIONS
|
||
|
||
|
||
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"
|
||
|
||
|
||
def read_tokens(input_file):
|
||
"""Читает токены из файла (русские названия типов)"""
|
||
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
|
||
|
||
|
||
# ==================== СОСТОЯНИЕ ПАРСЕРА ====================
|
||
# Хранится в словаре, передаётся во все функции явно
|
||
|
||
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
|
||
|
||
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] == 'СЛОВО':
|
||
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
|
||
|
||
return parse_instruction_or_directive(state)
|
||
|
||
|
||
# ==================== ГЛАВНЫЙ ЦИКЛ ====================
|
||
|
||
def parse_all(tokens):
|
||
"""Разбирает все токены, возвращает список строк AST"""
|
||
state = parser_init(tokens)
|
||
while state['pos'] < len(state['tokens']):
|
||
parse_line(state)
|
||
return state['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)
|
||
ast_lines = parse_all(tokens)
|
||
write_ast(ast_lines, output_file)
|
||
print(f"Парсер: {len(ast_lines)} строк AST записано в {output_file}") |