349 lines
15 KiB
Python
349 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
Второй проход КВС
|
||
Генерирует машинный код и создаёт CSV-файл
|
||
"""
|
||
|
||
import sys
|
||
import struct
|
||
sys.path.insert(0, '.')
|
||
from kvs_data import PAGE_SIZE, align_up
|
||
from kvs_pass2_encoder import encode_instruction, parse_operand
|
||
|
||
|
||
def unescape_string(s):
|
||
"""Преобразует escape-последовательности в реальные символы"""
|
||
result = []
|
||
i = 0
|
||
while i < len(s):
|
||
if s[i] == '\\' and i + 1 < len(s):
|
||
if s[i + 1] == 'n':
|
||
result.append('\n')
|
||
elif s[i + 1] == 't':
|
||
result.append('\t')
|
||
elif s[i + 1] == 'r':
|
||
result.append('\r')
|
||
else:
|
||
result.append(s[i])
|
||
result.append(s[i + 1])
|
||
i += 2
|
||
else:
|
||
result.append(s[i])
|
||
i += 1
|
||
return ''.join(result)
|
||
|
||
|
||
def read_ast(input_file):
|
||
"""Читает AST из файла"""
|
||
ast_lines = []
|
||
with open(input_file, 'r', encoding='utf-8') as f:
|
||
for line in f:
|
||
ast_lines.append(line.strip())
|
||
return ast_lines
|
||
|
||
|
||
def read_pass1(input_file):
|
||
"""
|
||
Читает результаты первого прохода:
|
||
- PARAM: параметры компоновки (размеры, адреса)
|
||
- LABEL: метки и их позиции
|
||
- SYMBOL: константы
|
||
"""
|
||
data = {}
|
||
labels = {}
|
||
label_sections = {}
|
||
symbols = {}
|
||
|
||
with open(input_file, 'r', encoding='utf-8') as f:
|
||
for line in f:
|
||
line = line.strip()
|
||
if not line:
|
||
continue
|
||
parts = line.split(':')
|
||
if parts[0] == "PARAM":
|
||
key = parts[1]
|
||
value = parts[2]
|
||
if value.isdigit() or (value.startswith('-') and value[1:].isdigit()):
|
||
value = int(value)
|
||
data[key] = value
|
||
elif parts[0] == "LABEL":
|
||
label = parts[1]
|
||
sec = parts[2]
|
||
pos = int(parts[3])
|
||
labels[label] = pos
|
||
label_sections[label] = sec
|
||
elif parts[0] == "SYMBOL":
|
||
name = parts[1]
|
||
value = int(parts[2])
|
||
symbols[name] = value
|
||
|
||
return data, labels, label_sections, symbols
|
||
|
||
|
||
# ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ПАРСИНГА AST ==========
|
||
|
||
def parse_ast_line(line):
|
||
"""
|
||
Универсальный разбор строки AST.
|
||
Возвращает (тип, словарь_полей).
|
||
|
||
Форматы AST:
|
||
- DIRECTIVE:имя
|
||
- DIRECTIVE:имя:секция
|
||
- DIRECTIVE:имя:секция:данные
|
||
- DIRECTIVE:имя:имя_константы:значение
|
||
- LABEL:имя:секция
|
||
- INSTR:мнемоника:секция:операнд1,операнд2,...
|
||
|
||
Операнды могут содержать двоеточия (MEM:reg_indirect:рбикс).
|
||
Поэтому для INSTR используется специальный разбор: первые 3 двоеточия
|
||
отделяют служебные поля, остальное — операнды.
|
||
"""
|
||
if not line:
|
||
return None, {}
|
||
|
||
line_type = line.split(':')[0]
|
||
|
||
if line_type == "INSTR":
|
||
# Формат: INSTR:мнемоника:секция:операнды
|
||
# Находим первые три двоеточия
|
||
first_colon = line.find(':')
|
||
second_colon = line.find(':', first_colon + 1)
|
||
third_colon = line.find(':', second_colon + 1)
|
||
|
||
if first_colon == -1 or second_colon == -1 or third_colon == -1:
|
||
return "INSTR", {}
|
||
|
||
mnemonic = line[first_colon + 1 : second_colon]
|
||
section = line[second_colon + 1 : third_colon]
|
||
operands_str = line[third_colon + 1 :]
|
||
|
||
operands = []
|
||
if operands_str:
|
||
operands = operands_str.split(',')
|
||
|
||
return "INSTR", {
|
||
'mnemonic': mnemonic,
|
||
'section': section,
|
||
'operands': operands,
|
||
'operands_str': operands_str
|
||
}
|
||
|
||
elif line_type == "DIRECTIVE":
|
||
parts = line.split(':')
|
||
directive = parts[1] if len(parts) > 1 else ""
|
||
|
||
if directive in ('.текст', '.данные', '.бнд'):
|
||
return "DIRECTIVE", {'directive': directive}
|
||
|
||
elif directive == '.глобал':
|
||
label = parts[2] if len(parts) > 2 else ""
|
||
return "DIRECTIVE", {'directive': directive, 'label': label}
|
||
|
||
elif directive in ('.строка_нуль', '.строка'):
|
||
sec = parts[2] if len(parts) > 2 else ""
|
||
s = parts[3] if len(parts) > 3 else ""
|
||
return "DIRECTIVE", {'directive': directive, 'section': sec, 'string': s}
|
||
|
||
elif directive == '.константа':
|
||
name = parts[2] if len(parts) > 2 else ""
|
||
value = parts[3] if len(parts) > 3 else ""
|
||
return "DIRECTIVE", {'directive': directive, 'name': name, 'value': value}
|
||
|
||
elif directive == '.байт':
|
||
sec = parts[2] if len(parts) > 2 else ""
|
||
bytes_str = parts[3] if len(parts) > 3 else ""
|
||
return "DIRECTIVE", {'directive': directive, 'section': sec, 'bytes': bytes_str}
|
||
|
||
elif directive in ('.резб', '.резс', '.рездс', '.резкс'):
|
||
sec = parts[2] if len(parts) > 2 else ""
|
||
count = parts[3] if len(parts) > 3 else "0"
|
||
return "DIRECTIVE", {'directive': directive, 'section': sec, 'count': count}
|
||
|
||
else:
|
||
return "DIRECTIVE", {'directive': directive}
|
||
|
||
elif line_type == "LABEL":
|
||
parts = line.split(':')
|
||
label_name = parts[1] if len(parts) > 1 else ""
|
||
sec = parts[2] if len(parts) > 2 else ""
|
||
return "LABEL", {'name': label_name, 'section': sec}
|
||
|
||
return line_type, {}
|
||
|
||
|
||
# ========== ОСНОВНОЙ КЛАСС ВТОРОГО ПРОХОДА ==========
|
||
|
||
class Pass2:
|
||
def __init__(self, pass1_data, labels, label_sections, symbols):
|
||
self.labels = labels
|
||
self.label_sections = label_sections
|
||
self.symbols = symbols
|
||
self.vaddr_text = pass1_data["vaddr_text"]
|
||
self.vaddr_data = pass1_data.get("vaddr_data", align_up(self.vaddr_text + pass1_data["text_size"], PAGE_SIZE))
|
||
self.vaddr_bnd = pass1_data.get("vaddr_bnd", self.vaddr_data + PAGE_SIZE)
|
||
self.position = {".text": 0, ".data": 0}
|
||
self.current_section = ".text"
|
||
self.csv_lines = []
|
||
self.data_bytes = {".text": bytearray(), ".data": bytearray()}
|
||
|
||
def get_target_addr(self, mnemonic, operands):
|
||
"""Вычисляет целевой адрес для инструкций перехода"""
|
||
jump_instructions = {
|
||
"переход", "короткий_переход", "вызвать", "цикл",
|
||
"переход_если_равно", "переход_если_неравно", "переход_если_меньше",
|
||
"переход_если_больше", "переход_если_меньше_или_равно", "переход_если_больше_или_равно",
|
||
"переход_если_перенос", "переход_если_нет_переноса", "переход_если_ноль", "переход_если_не_ноль",
|
||
"короткий_переход_если_равно", "короткий_переход_если_неравно", "короткий_переход_если_меньше",
|
||
"короткий_переход_если_больше", "короткий_переход_если_меньше_или_равно", "короткий_переход_если_больше_или_равно",
|
||
"короткий_переход_если_перенос", "короткий_переход_если_нет_переноса",
|
||
"короткий_переход_если_ноль", "короткий_переход_если_не_ноль"
|
||
}
|
||
|
||
try:
|
||
if mnemonic in jump_instructions:
|
||
target = parse_operand(operands[0], self.labels, self.label_sections,
|
||
self.symbols, self.vaddr_text, self.vaddr_data)
|
||
return hex(target)
|
||
elif mnemonic == "переместить_имм" and len(operands) >= 2:
|
||
op_str = operands[1]
|
||
if op_str in self.labels:
|
||
sec = self.label_sections[op_str]
|
||
if sec == ".text":
|
||
addr = self.labels[op_str] + self.vaddr_text
|
||
elif sec == ".data":
|
||
addr = self.labels[op_str] + self.vaddr_data
|
||
elif sec == ".бнд":
|
||
addr = self.labels[op_str] + self.vaddr_bnd
|
||
else:
|
||
addr = self.labels[op_str] + self.vaddr_text
|
||
return hex(addr)
|
||
elif op_str in self.symbols:
|
||
return hex(self.symbols[op_str])
|
||
except Exception:
|
||
return "ошибка"
|
||
|
||
return ""
|
||
|
||
def process_line(self, line):
|
||
"""Обрабатывает одну строку AST"""
|
||
line_type, fields = parse_ast_line(line)
|
||
|
||
if line_type == "DIRECTIVE":
|
||
directive = fields.get('directive', '')
|
||
|
||
if directive == '.текст':
|
||
self.current_section = ".text"
|
||
elif directive == '.данные':
|
||
self.current_section = ".data"
|
||
elif directive in ('.строка_нуль', '.строка'):
|
||
s = fields.get('string', '')
|
||
real_s = unescape_string(s)
|
||
bstring = real_s.encode('utf-8')
|
||
if directive == '.строка_нуль':
|
||
bstring += b'\x00'
|
||
|
||
original_cmd = f'{directive} "{s}"'
|
||
start_addr = self.vaddr_data + self.position[".data"]
|
||
|
||
for i, byte in enumerate(bstring):
|
||
addr = start_addr + i
|
||
byte_hex = "{:02X}".format(byte)
|
||
cmd = original_cmd if i == 0 else ""
|
||
self.csv_lines.append((hex(addr), byte_hex, "", cmd))
|
||
|
||
self.data_bytes[".data"] += bstring
|
||
self.position[".data"] += len(bstring)
|
||
|
||
elif directive == '.байт':
|
||
bytes_str = fields.get('bytes', '')
|
||
if bytes_str:
|
||
byte_values = bytes_str.split(',')
|
||
original_cmd = f".байт {bytes_str}"
|
||
start_addr = self.vaddr_data + self.position[".data"]
|
||
|
||
for i, byte_str in enumerate(byte_values):
|
||
if byte_str.startswith('0x'):
|
||
val = int(byte_str, 16)
|
||
elif byte_str.isdigit() or (byte_str.startswith('-') and byte_str[1:].isdigit()):
|
||
val = int(byte_str)
|
||
else:
|
||
continue
|
||
|
||
addr = start_addr + i
|
||
byte_hex = "{:02X}".format(val & 0xFF)
|
||
cmd = original_cmd if i == 0 else ""
|
||
self.csv_lines.append((hex(addr), byte_hex, "", cmd))
|
||
self.data_bytes[".data"].append(val & 0xFF)
|
||
|
||
self.position[".data"] += len(byte_values)
|
||
|
||
elif line_type == "LABEL":
|
||
label_name = fields.get('name', '')
|
||
sec = fields.get('section', '')
|
||
if label_name in self.labels:
|
||
self.position[sec] = self.labels[label_name]
|
||
|
||
elif line_type == "INSTR":
|
||
mnemonic = fields.get('mnemonic', '')
|
||
sec = fields.get('section', '')
|
||
operands = fields.get('operands', [])
|
||
|
||
if operands:
|
||
original_cmd = f"{mnemonic} {', '.join(operands)}"
|
||
else:
|
||
original_cmd = mnemonic
|
||
|
||
current_pos = self.position[sec]
|
||
code = encode_instruction(mnemonic, operands, self.labels, self.label_sections,
|
||
self.symbols, self.vaddr_text, self.vaddr_data, current_pos,
|
||
self.vaddr_bnd)
|
||
|
||
target_addr = self.get_target_addr(mnemonic, operands)
|
||
|
||
start_addr = (self.vaddr_text if sec == ".text" else self.vaddr_data) + current_pos
|
||
|
||
for i, byte in enumerate(code):
|
||
addr = start_addr + i
|
||
byte_hex = "{:02X}".format(byte)
|
||
cmd = original_cmd if i == 0 else ""
|
||
addr_target = target_addr if i == 0 else ""
|
||
self.csv_lines.append((hex(addr), byte_hex, addr_target, cmd))
|
||
|
||
self.data_bytes[sec] += code
|
||
self.position[sec] += len(code)
|
||
|
||
def process_all(self, ast_lines):
|
||
"""Обрабатывает все строки AST"""
|
||
for line in ast_lines:
|
||
self.process_line(line)
|
||
return self.csv_lines
|
||
|
||
|
||
def write_csv(csv_lines, output_file):
|
||
"""Записывает сгенерированные байты в CSV файл"""
|
||
with open(output_file, 'w', encoding='utf-8') as f:
|
||
f.write("адрес;байт;целевой_адрес;исходная_команда\n")
|
||
for addr, byte, target, cmd in csv_lines:
|
||
if cmd and (';' in cmd or '\n' in cmd):
|
||
cmd = '"' + cmd.replace('"', '""') + '"'
|
||
f.write(f"{addr};{byte};{target};{cmd}\n")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
if len(sys.argv) != 4:
|
||
print("Использование: python kvs_pass2.py <вход.аст> <вход.проход1> <выход.csv>")
|
||
sys.exit(1)
|
||
|
||
ast_lines = read_ast(sys.argv[1])
|
||
pass1_data, labels, label_sections, symbols = read_pass1(sys.argv[2])
|
||
|
||
pass2 = Pass2(pass1_data, labels, label_sections, symbols)
|
||
csv_lines = pass2.process_all(ast_lines)
|
||
write_csv(csv_lines, sys.argv[3])
|
||
|
||
print(f"Проход 2: {len(csv_lines)} строк CSV записано в {sys.argv[3]}")
|
||
print(f" .text: {pass2.position['.text']} байт, виртуальный адрес: 0x{pass2.vaddr_text:x}")
|
||
print(f" .data: {pass2.position['.data']} байт, виртуальный адрес: 0x{pass2.vaddr_data:x}") |