kvs/kvs_pass2.py

349 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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}")