Важную роль при обработке файлов играет валидация. Для упрощения в прошлой темах проверялась лишь корректность инструкций, а валидация операндов была опущена, соответственно, если в исходном файле вдруг появится какая-то ошибка - некорректное название регистра или некорректный литерал, то при запуске программы мы столкнемся с ошибкой интерпретатора Python, наподобие следующего:
Traceback (most recent call last): File "/python/asm/app.py", line 61, in <module> else: op2 = int(tokens[2]) ^^^^^^^^^^^^^^ ValueError: invalid literal for int() with base 10: 'a0'
В принципе мы можем так и оставить. В конце концов, если один операнд неправильный, то, может быть, нет смысла выполнять дальше программу. Но, возможно мы бы захотели использовать другой подход, например, программно завершить приложение, выведя какое-нибудь сообщение об ошибке и скрыть от пользователя детали ошибки интерпретатора Python. Для этого возьмем программу из прошлой темы и изменим ее следующим образом:
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: return int(token) # преобразуем в число и возвращаем except ValueError: # если ошибка, выводим сообщение print("Некорректный токен", token) return None # и возвращаем 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)) # логгируем состояние программы на консоль
В отличие от прошлых тем теперь валидация инструкции и ее параметров вынесена в отдельную функцию - get_opCount
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
Функция получает набор токенов инструкции и проверяет корректность инструкции и количество ее параметров. В качестве результата функция возвращает количество ее параметров. Если возникла ошибка, то выводим сообщение об ошибке и возвращаем None.
Далее в бесконечном цикле обработке инструкций обращаемся к этой функции, и если ее результат None, то выходим из цикла:
opCount = get_opCount(tokens) # получаем количество операндов if(opCount == None): break
Получение операндов также вынесено в отдельные функции. Например, определена функция получения индекса регистра get_register_index
def get_register_index(token, show_error): if (token in regs32): return regs32[token] if show_error: print("Некорректный регистр", token) return None
Если регистр не найден, возвращаем None. Также если второй параметр функции - show_error равен True, то выводим сообщение об ошибке.
Второй операнд некоторых инструкций может представлять только регистр. Для получения его значения определяем функцию get_register
def get_register(token, show_error): rInd = get_register_index(token, show_error) if (rInd != None): return r[rInd] return None
Эта функция сначала обращается к выше определенной функции get_register_index. Если регистр найден, то возвращаем его значение. Если нет, то возвращаем None, а функция get_register_index выведет сообщение об ошибке в зависимости от значения show_error
Получение литерала также вынесено в отдельную функцию - get_literal:
def get_literal(token): try: return int(token) # преобразуем в число и возвращаем except ValueError: # если ошибка, выводим сообщение print("Некорректный токен", token) return None # и возвращаем None
Для простоты здесь пытаемся преобразовать токен в число. При успешном преобразовании возвращаем полученное число. Если преобразование выполнить не удалось, то генерируется ошибка ValueError. В этом случае выводим сообщение об ошибке и возвращаем None.
В принципе здесь можно было бы обойтись без конструкции try..except и проверять, например, с помощью функции isnumeric(). Но поскольку в данном случае у нас также есть отрицательные числа, то это усложнит логику. А добавление других типов литералов в следующих статьях сделает логику еще более сложной. Поэтому для простоты применяем именно try..except.
Третий операнд большинства инструкций и второй операнд инструкции MOV может принимать регистр или литерал, для этого определяем функцию:
def get_register_or_literal(token): reg = get_register(token, False) if (reg != None): return reg return get_literal(token)
Здесь сначала обращаемся к функции get_register, пытаясь получить регистр. Поскольку токен может также представлять литерал, то нам не надо при отсутствии регистра выводить сообщение об ошибке, поэтому в get_register вторым параметром передаем False.
В бесконечном цикле получаем операнды:
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
Если какой-то операнд не определен, то он будет равен None. С помощью выражения None in [op1, op2, op3]
мы можем проверить эту ситуацию и выйти из цикла.
Обратите внимание, что даже если инструкция не использует третий операнд (инструкция MOV), то он никогда не будет равен None, так как по умолчанию он равен 0 и далее никак не изменяется. Операнд может получить значение None, если только при его установке
произошла ошибка.
Для проверки определим следующий файл "hello.s":
mov r3, 5 mov r9, 3 mov r0, 6
В итоге при выполнении программы мы получим следующий вывод:
pc:1 mov r3 5 r0:0x0000 r1:0x0000 r2:0x0000 r3:0x0005 Некорректный регистр r9
Другой пример:
mov r0, 5 mov r1, asm mov r2, 7
На второй строке у нас некорректный токен. Поэтому при выполнении программы мы получим следующий вывод:
pc:1 mov r0 5 r0:0x05 r1:0x00 r2:0x00 r3:0x00 Некорректный токен asm