337 lines
13 KiB
Python
337 lines
13 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
Тестовый скрипт для проверки инструкций с фиксированной длиной
|
||
Запуск: python3 test_fixed_instructions.py [--keep-files]
|
||
"""
|
||
|
||
import sys
|
||
import os
|
||
import subprocess
|
||
import csv
|
||
|
||
# ANSI color codes
|
||
GREEN = '\033[92m'
|
||
RED = '\033[91m'
|
||
YELLOW = '\033[93m'
|
||
RESET = '\033[0m'
|
||
|
||
# === ТЕСТОВЫЙ ИСХОДНИК ===
|
||
TEST_SOURCE = '''.глобал _start
|
||
.текст
|
||
|
||
_start:
|
||
нет_операции
|
||
вызов_системы
|
||
вернуться
|
||
втолкнуть раикс
|
||
втолкнуть р8
|
||
вытолкнуть раикс
|
||
вытолкнуть р8
|
||
переместить раикс, рбикс
|
||
переместить р8, р9
|
||
сравнить раикс, рбикс
|
||
сравнить р8, р9
|
||
проверить раикс, рбикс
|
||
проверить р8, р9
|
||
прибавить раикс, рбикс
|
||
прибавить р8, р9
|
||
вычесть раикс, рбикс
|
||
вычесть р8, р9
|
||
и раикс, рбикс
|
||
и р8, р9
|
||
или раикс, рбикс
|
||
или р8, р9
|
||
исключающее_или раикс, рбикс
|
||
исключающее_или р8, р9
|
||
увеличить раикс
|
||
увеличить р8
|
||
увеличить еаикс
|
||
уменьшить раикс
|
||
уменьшить р8
|
||
уменьшить еаикс
|
||
инвертировать раикс
|
||
инвертировать еаикс
|
||
отрицать раикс
|
||
отрицать еаикс
|
||
переместить_имм раикс, 10
|
||
переместить_имм рбикс, 5
|
||
умножить рбикс
|
||
переместить_имм раикс, 100
|
||
переместить_имм рбикс, 7
|
||
разделить рбикс
|
||
умножить_знаковое рбикс
|
||
разделить_знаковое рбикс
|
||
сдвиг_влево раикс, 2
|
||
сдвиг_вправо раикс, 2
|
||
сдвиг_арифметический_вправо раикс, 2
|
||
вращать_влево раикс, 2
|
||
вращать_вправо раикс, 2
|
||
переход метка_длинная
|
||
короткий_переход метка_короткая
|
||
цикл метка_короткая
|
||
вызвать метка_длинная
|
||
|
||
метка_длинная:
|
||
нет_операции
|
||
нет_операции
|
||
нет_операции
|
||
|
||
метка_короткая:
|
||
нет_операции
|
||
|
||
переместить_имм раикс, 60
|
||
переместить_имм рдикс, 0
|
||
вызов_системы
|
||
'''
|
||
|
||
|
||
# === ОЖИДАЕМЫЕ РЕЗУЛЬТАТЫ ===
|
||
# Для инструкций с фиксированным кодом — полная проверка
|
||
EXPECTED_INSTRUCTIONS_FULL = {
|
||
"нет_операции": [0x90],
|
||
"вызов_системы": [0x0F, 0x05],
|
||
"вернуться": [0xC3],
|
||
"втолкнуть раикс": [0x50],
|
||
"втолкнуть р8": [0x41, 0x50],
|
||
"вытолкнуть раикс": [0x58],
|
||
"вытолкнуть р8": [0x41, 0x58],
|
||
"переместить раикс, рбикс": [0x48, 0x89, 0xD8],
|
||
"переместить р8, р9": [0x4D, 0x89, 0xC8],
|
||
"сравнить раикс, рбикс": [0x48, 0x39, 0xD8],
|
||
"сравнить р8, р9": [0x4D, 0x39, 0xC8],
|
||
"проверить раикс, рбикс": [0x48, 0x85, 0xD8],
|
||
"проверить р8, р9": [0x4D, 0x85, 0xC8],
|
||
"прибавить раикс, рбикс": [0x48, 0x01, 0xD8],
|
||
"прибавить р8, р9": [0x4D, 0x01, 0xC8],
|
||
"вычесть раикс, рбикс": [0x48, 0x29, 0xD8],
|
||
"вычесть р8, р9": [0x4D, 0x29, 0xC8],
|
||
"и раикс, рбикс": [0x48, 0x21, 0xD8],
|
||
"и р8, р9": [0x4D, 0x21, 0xC8],
|
||
"или раикс, рбикс": [0x48, 0x09, 0xD8],
|
||
"или р8, р9": [0x4D, 0x09, 0xC8],
|
||
"исключающее_или раикс, рбикс": [0x48, 0x31, 0xD8],
|
||
"исключающее_или р8, р9": [0x4D, 0x31, 0xC8],
|
||
"увеличить раикс": [0x48, 0xFF, 0xC0],
|
||
"увеличить р8": [0x49, 0xFF, 0xC0],
|
||
"увеличить еаикс": [0xFF, 0xC0],
|
||
"уменьшить раикс": [0x48, 0xFF, 0xC8],
|
||
"уменьшить р8": [0x49, 0xFF, 0xC8],
|
||
"уменьшить еаикс": [0xFF, 0xC8],
|
||
"инвертировать раикс": [0x48, 0xF7, 0xD0],
|
||
"инвертировать еаикс": [0xF7, 0xD0],
|
||
"отрицать раикс": [0x48, 0xF7, 0xD8],
|
||
"отрицать еаикс": [0xF7, 0xD8],
|
||
"умножить рбикс": [0x48, 0xF7, 0xE0],
|
||
"разделить рбикс": [0x48, 0xF7, 0xF0],
|
||
"умножить_знаковое рбикс": [0x48, 0xF7, 0xE8],
|
||
"разделить_знаковое рбикс": [0x48, 0xF7, 0xF8],
|
||
"сдвиг_влево раикс, 2": [0x48, 0xC1, 0xE0, 0x02],
|
||
"сдвиг_вправо раикс, 2": [0x48, 0xC1, 0xE8, 0x02],
|
||
"сдвиг_арифметический_вправо раикс, 2": [0x48, 0xC1, 0xF8, 0x02],
|
||
"вращать_влево раикс, 2": [0x48, 0xC1, 0xC0, 0x02],
|
||
"вращать_вправо раикс, 2": [0x48, 0xC1, 0xC8, 0x02],
|
||
"переместить_имм раикс, 10": [0x48, 0xB8, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||
"переместить_имм рбикс, 5": [0x48, 0xBB, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||
"переместить_имм раикс, 100": [0x48, 0xB8, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||
"переместить_имм рбикс, 7": [0x48, 0xBB, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||
"переместить_имм раикс, 60": [0x48, 0xB8, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||
"переместить_имм рдикс, 0": [0x48, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||
}
|
||
|
||
# Инструкции, у которых проверяем только opcode (первые байты)
|
||
EXPECTED_INSTRUCTIONS_PREFIX = {
|
||
"переход метка_длинная": [0xE9], # JMP rel32
|
||
"короткий_переход метка_короткая": [0xEB], # JMP SHORT
|
||
"цикл метка_короткая": [0xE2], # LOOP
|
||
"вызвать метка_длинная": [0xE8], # CALL rel32
|
||
}
|
||
|
||
|
||
def read_csv_instructions(csv_file):
|
||
"""Читает CSV и возвращает словарь {мнемоника: [байты]}"""
|
||
instructions = {}
|
||
current_mnemonic = None
|
||
current_bytes = []
|
||
|
||
with open(csv_file, 'r', encoding='utf-8') as f:
|
||
reader = csv.reader(f, delimiter=';')
|
||
header = next(reader)
|
||
|
||
for row in reader:
|
||
if len(row) < 4:
|
||
continue
|
||
|
||
cmd = row[3].strip()
|
||
byte_str = row[1].strip()
|
||
|
||
if not byte_str:
|
||
continue
|
||
|
||
byte_val = int(byte_str, 16)
|
||
|
||
if cmd:
|
||
if current_mnemonic and current_bytes:
|
||
instructions[current_mnemonic] = current_bytes
|
||
current_mnemonic = cmd
|
||
current_bytes = [byte_val]
|
||
else:
|
||
if current_mnemonic:
|
||
current_bytes.append(byte_val)
|
||
|
||
if current_mnemonic and current_bytes:
|
||
instructions[current_mnemonic] = current_bytes
|
||
|
||
return instructions
|
||
|
||
|
||
def run_tests(generated_instructions):
|
||
"""Сравнивает сгенерированные инструкции с ожидаемыми"""
|
||
errors = []
|
||
missing = []
|
||
|
||
# Проверяем инструкции с полным совпадением
|
||
for mnemonic, expected_bytes in EXPECTED_INSTRUCTIONS_FULL.items():
|
||
if mnemonic not in generated_instructions:
|
||
missing.append(mnemonic)
|
||
continue
|
||
|
||
actual_bytes = generated_instructions[mnemonic]
|
||
if actual_bytes != expected_bytes:
|
||
expected_hex = ' '.join(f'{b:02X}' for b in expected_bytes)
|
||
actual_hex = ' '.join(f'{b:02X}' for b in actual_bytes)
|
||
errors.append(f" {mnemonic}: ожидалось [{expected_hex}], получено [{actual_hex}]")
|
||
|
||
# Проверяем инструкции только по префиксу
|
||
for mnemonic, expected_prefix in EXPECTED_INSTRUCTIONS_PREFIX.items():
|
||
if mnemonic not in generated_instructions:
|
||
missing.append(mnemonic)
|
||
continue
|
||
|
||
actual_bytes = generated_instructions[mnemonic]
|
||
prefix_len = len(expected_prefix)
|
||
|
||
if len(actual_bytes) < prefix_len:
|
||
errors.append(f" {mnemonic}: слишком короткая ({len(actual_bytes)} байт, ожидалось минимум {prefix_len})")
|
||
elif actual_bytes[:prefix_len] != expected_prefix:
|
||
expected_hex = ' '.join(f'{b:02X}' for b in expected_prefix)
|
||
actual_hex = ' '.join(f'{b:02X}' for b in actual_bytes[:prefix_len])
|
||
errors.append(f" {mnemonic}: префикс [{expected_hex}...], получено [{actual_hex}...]")
|
||
|
||
return errors, missing
|
||
|
||
|
||
def cleanup(files_to_delete, keep_files=False):
|
||
"""Удаляет временные файлы"""
|
||
if keep_files:
|
||
print("\nФлаг --keep-files указан, промежуточные файлы сохранены:")
|
||
for f in files_to_delete:
|
||
if os.path.exists(f):
|
||
print(f" {f}")
|
||
return
|
||
|
||
deleted = []
|
||
for f in files_to_delete:
|
||
if os.path.exists(f):
|
||
os.remove(f)
|
||
deleted.append(f)
|
||
|
||
if deleted:
|
||
print(f"\nУдалено {len(deleted)} временных файлов")
|
||
|
||
|
||
def main():
|
||
keep_files = '--keep-files' in sys.argv or '-k' in sys.argv
|
||
|
||
base_name = "т1"
|
||
source_file = f"{base_name}.квс"
|
||
tokens_file = f"{base_name}.токены"
|
||
ast_file = f"{base_name}.аст"
|
||
p1_file = f"{base_name}.проход1"
|
||
csv_file = f"{base_name}.csv"
|
||
elf_file = f"{base_name}.elf"
|
||
|
||
|
||
temp_files = [tokens_file, ast_file, p1_file, csv_file, elf_file]
|
||
|
||
print("КВС Тестировщик - Инструкции с фиксированной длиной\n")
|
||
print("=" * 60)
|
||
|
||
# Шаг 1: Создание исходного файла
|
||
print("Шаг 1: Создание тестового исходника...")
|
||
with open(source_file, 'w', encoding='utf-8') as f:
|
||
f.write(TEST_SOURCE)
|
||
print(f" Создан {source_file}")
|
||
|
||
# Шаг 2: Компиляция
|
||
print("\nШаг 2: Компиляция...")
|
||
result = subprocess.run(
|
||
[sys.executable, "kvs_build.py", source_file],
|
||
capture_output=True,
|
||
text=True
|
||
)
|
||
|
||
if result.returncode != 0:
|
||
print(" ОШИБКА компиляции:")
|
||
print(result.stderr)
|
||
print("\nОставшиеся файлы для отладки:")
|
||
print(f" {source_file}")
|
||
if os.path.exists(csv_file):
|
||
print(f" {csv_file}")
|
||
sys.exit(1)
|
||
|
||
print(" Компиляция успешна")
|
||
|
||
# Шаг 3: Проверка результатов
|
||
print("\nШаг 3: Проверка сгенерированного кода...")
|
||
|
||
if not os.path.exists(csv_file):
|
||
print(f" Файл {csv_file} не найден!")
|
||
sys.exit(1)
|
||
|
||
generated_instructions = read_csv_instructions(csv_file)
|
||
errors, missing = run_tests(generated_instructions)
|
||
|
||
# Вывод результатов
|
||
print("\n" + "=" * 60)
|
||
print("РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ:")
|
||
print("=" * 60)
|
||
|
||
if missing:
|
||
print(f"\n{YELLOW}ОТСУТСТВУЮТ ИНСТРУКЦИИ ({len(missing)}):{RESET}")
|
||
for m in missing[:10]:
|
||
print(f" - {m}")
|
||
if len(missing) > 10:
|
||
print(f" ... и ещё {len(missing)-10}")
|
||
|
||
if errors:
|
||
print(f"\n{RED}НАЙДЕНО {len(errors)} ОШИБОК В КОДЕ:{RESET}")
|
||
for err in errors:
|
||
print(err)
|
||
success = False
|
||
else:
|
||
print(f"\n{GREEN}НЕТ ОШИБОК! ВСЕ ИНСТРУКЦИИ ЗАКОДИРОВАНЫ ВЕРНО!{RESET}")
|
||
success = True
|
||
|
||
# Статистика
|
||
print(f"\nСтатистика:")
|
||
print(f" Ожидалось инструкций (полная проверка): {len(EXPECTED_INSTRUCTIONS_FULL)}")
|
||
print(f" Ожидалось инструкций (проверка префикса): {len(EXPECTED_INSTRUCTIONS_PREFIX)}")
|
||
print(f" Сгенерировано инструкций: {len(generated_instructions)}")
|
||
|
||
# Шаг 4: Очистка
|
||
cleanup([source_file] + temp_files, keep_files)
|
||
|
||
# Финальный вердикт
|
||
print("\n" + "=" * 60)
|
||
if success:
|
||
print(f"{GREEN}ТЕСТЫ ПРОЙДЕНЫ УСПЕШНО!{RESET}")
|
||
sys.exit(0)
|
||
else:
|
||
print(f"{RED}ТЕСТЫ НЕ ПРОЙДЕНЫ!{RESET}")
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |