Валидация входных данных

Последнее обновление: 01.03.2024

Важную роль при обработке файлов играет валидация. Для упрощения в прошлой темах проверялась лишь корректность инструкций, а валидация операндов была опущена, соответственно, если в исходном файле вдруг появится какая-то ошибка - некорректное название регистра или некорректный литерал, то при запуске программы мы столкнемся с ошибкой интерпретатора 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
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850