Инструкции могут принимать литералы, которые еще называются непосредственными операндами или immediate operand и которые по сути представляют обычные числа. Однако существуют разные способы определения подобных значений. Так, мы можем определить непосредственный операнд в виде десятичного числа. Например:
mov r1, 23
В данном случае в регистр r1 помещается число 23.
Однако это запись не единственная. Например, в языке ассемблере от GNU для ARM непосредственные операнды также предварялись символом решетки:
mov r1, #23
Также литералы могут быть определены в шестнадцатеричной форме:
mov r1, 0x17 // r1 = 23
Обычно шестнадцатеричные числа предваряются символами 0x
Другая распространенная запись - запись в бинарном формате
mov r2, 0b10111 // r2 = 23
Бинарные числа предваряются символами 0b
В зависимости от конкретного языка ассемблера запись в том или ином формате может отличаться, а также могут быть иные типы литералов. Например, одиночные символы, которые помещаются в одинарные кавычки или строки в двойных кавычках. В данном же случае ограничимся бинарными, шестнадцатеричными и десятичными литералами.
В прошлой теме за парсинг литералов отвечала функция get_literal, которая выглядела следующим образом:
def get_literal(token):
try:
return int(token) # преобразуем в число и возвращаем
except ValueError: # если ошибка, выводим сообщение
print("Некорректный токен", token)
return None # и возвращаем None
Изменим ее, добавим поддержку бинарных и шестнадцатеричных литералов:
def get_literal(token):
try:
if (token[0:2]=="0x"): return int(token[2:],16) # если 16-ричное число
elif (token[0:2]=="0b"): return int(token[2:],2) # если двоичное число
return int(token) # если десятичное число
except ValueError:
print("Некорректный токен", token)
return None
Если токен начинается на "0x" или "0b", то рассматриваем его соответственно как 16-ричное или двоичное число. Иначе преобразуем значение в число.
Полная программа симулятора ассемблера:
instructions = [] # список инструкций
r = [0]*4 # значения 4 регистров
# карта сопоставления регистров и их индексов в списке r
regs32 = {"r0":0, "r1":1, "r2":2, "r3":3}
pc = 0 # указатель на следующую инструкцию
# поддерживаемые инструкции и количество их операндов
mnemonics = {"mov":2, "add": 3, "sub":3, "mul": 3, "and": 3, "orr": 3}
# считываем файл hello.s в список инструкций
with open("hello.s", "r", encoding="utf8") as source:
lines = source.readlines()
# обрабатываем строки из файла
for i in range(0,len(lines)):
lines[i] = lines[i].split("//")[0] \
.replace(",", " ") \
.strip().rstrip("\n") \
.lower()
while " " in lines[i]: # заменяем несколько пробелов одним
lines[i] = lines[i].replace(" ", " ")
if(lines[i]) == "": continue # если получилась пустая строка, переходим к следующей строке
tokens = lines[i].split(" ") # разбиваем инструкцию на токены
instructions.append(tokens) # добавляем инструкцию в список instructions
# функций вывода состояния программы на консоль
def print_state(instruction):
print(f"pc:{pc}", end=" ") # выводим значение PC
print(f"{instruction:<16}", end=" ") # выводим текущую инструкцию
for reg in regs32: # выводим регистры
rInd = regs32[reg]
print(f"{reg}:{r[rInd]:<3}", end=" ")
print()
# получаем индекс регистра
def get_register_index(token, show_error):
if (token in regs32): return regs32[token]
if show_error: print("Некорректный регистр", token)
return None
# получаем значение регистра
def get_register(token, show_error):
rInd = get_register_index(token, show_error)
if (rInd != None): return r[rInd]
return None
# получаем литерал
def get_literal(token):
try:
if (token[0:2]=="0x"): return int(token[2:],16) # если 16-ричное число
elif (token[0:2]=="0b"): return int(token[2:],2) # если двоичное число
return int(token) # если десятичное число
except ValueError:
print("Некорректный токен", token)
return None
# получаем операнд, который может быть регистром или литералом
def get_register_or_literal(token):
reg = get_register(token, False)
if (reg != None): return reg
return get_literal(token)
# получаем тип инструкции
def get_opCount(tokens):
if tokens[0] not in mnemonics: # проверяем корректность инструкции
print("Некорректная инструкция ", " ".join(tokens))
return None
# получаем количество операндов для данной инструкции
count = mnemonics[tokens[0]]
if count!= len(tokens[1:]): # проверяем количество операндов
print("Некорректное количество операндов для инструкции: ", " ".join(tokens))
return None
return count
# цикл обработки инструкций
while True:
if pc >= len(instructions): break # если инструкции закончились, то выход из цикла
tokens = instructions[pc] # получаем текущую инструкцию для выполнения
pc = pc + 1 # увеличиваем указатель инструкций
opCount = get_opCount(tokens) # получаем количество операндов
if(opCount == None): break
# получаем операнды
op2, op3 = 0, 0
# первый операнд всегда регистр
op1=get_register_index(tokens[1], True)
# если инструкция с 2-мя операндами, то второй операнд может быть регистром или литералом
if(opCount==2): op2 = get_register_or_literal(tokens[2])
# если инструкция с 3-мя операндами, то второй операнд может быть регистром
# а третий операнд может быть регистром или литералом
if(opCount==3):
op2 = get_register(tokens[2], True)
op3 = get_register_or_literal(tokens[3])
# если какой-то параметр не установлен, завершаем цикл
if(None in [op1, op2, op3]): break
match tokens[0]:
case "mov":
r[op1] = op2
case "add":
r[op1] = op2 + op3
case "sub":
r[op1] = op2 - op3
case "mul":
r[op1] = op2 * op3
case "and":
r[op1] = op2 & op3
case "orr":
r[op1]= op2 | op3
print_state(" ".join(tokens)) # логгируем состояние программы на консоль
Для тестирования определим следующий файл "hello.s":
// тестовая программа на ассемблере mov r1, 0x17 // r1 = 23 mov r2, 0b10111 // r2 = 23 add r3, r1, r2 add r0, r3, -3
В этом случае мы получим следующий программный вывод:
pc:1 mov r1 0x17 r0:0 r1:23 r2:0 r3:0 pc:2 mov r2 0b10111 r0:0 r1:23 r2:23 r3:0 pc:3 add r3 r1 r2 r0:0 r1:23 r2:23 r3:46 pc:4 add r0 r3 -3 r0:43 r1:23 r2:23 r3:46