Реклама:

Цель второго прохода - создать объектную программу и напечатать протокол ассемблирования (если нужно). Кроме того, при втором проходе должна выводиться информация, необходимая для компоновки в один исполняемый файл процедур, которые ассемблировались в разное время. В листинге 7.10 показана процедура для второго прохода.

Листинг 7.10. Второй проход простого ассемблера

public static void pass_two() { // Эта процедура - второй проход ассемблера

boolean morejnput = true; // флаг, останавливающий второй проход

String line, opcode; // поля команды

int location_counter, length, type; // переменные final int END_STATEMENT = -2; // сигналы окончания ввода

final int MAX_C0DE =16; // максимальное число байтов в команде

byte code[] = new byte[MAX_CODE]; // число байтов в команде

// в порожденном коде

location_counter =0; // ассемблирование первой

// команды в адресе О

while (morejnput) { // morejnput с помощью директивы END

// получает значение "ложь"

type = read_type(); // считывание поля типа следующей строки

opcode = read_opcode(); // считывание поля кода операции

//в следующей строке

length = read_length(); // считывание поля длины

// в следующей строке

line = read_line(); // считывание самой входной строки

if (type != 0) { // тип 0 указывает на комментарий

switch(type) { // порождение выходного кода

case 1:eva1_typel(opcode, length, line, code); break; case 2: evalj:ype2(opcode, length, line, code); break; // другие варианты

}

}

write_output(code); // запись двоичного кода

writejisting(code, line); // вывод на печать одной строки

location_counter = location_counter + length; // обновление счетчика

// адресов команд if (type == END_STATEMENT) { // завершен ли ввод? more_input я false; // если да. то выполняем

// служебные операции finish_up(); // завершение

}

}

}

Процедура второго прохода похожа на процедуру первого: строки считывают-ся по одной и обрабатываются тоже по одной. Поскольку мы записали в начале каждой строки тип, код операции и длину (во временном файле), все они считы-ваются и, таким образом, нам не нужно проводить анализ строк во второй раз. Основная работа по порождению кода выполняется процедурами evaltypel, eva!_type2 и т. д. Каждая из них обрабатывает определенную модель (например, код операции и два регистра-операнда). Полученный в результате двоичный код команды сохраняется в переменной code. Затем совершается контрольное считывание. Желательно, чтобы процедура writecode просто сохраняла в буфере накопленный двоичный код и записывала файл на диск большими кусками - это снизит нагрузку на диск.

Исходный оператор и выходной (объектный) код, полученный из него (в ше-стнадцатеричной системе), можно напечатать или поместить в буфер, чтобы напечатать потом. После смены значения счетчика адресов команд вызывается следующий оператор.

До сих пор мы считали, что исходная программа не содержит ошибок. Однако любой, кто хоть когда-нибудь занимался программированием, знает, насколько это предположение не соответствует действительности. Вот только наиболее распространенные ошибки:

♦ используемый символ не определен;

♦ символ определен более одного раза;

♦ имя в поле кода операции не является допустимым кодом операции;

♦ слишком мало операндов для данного кода операции;

♦ слишком много операндов для данного кода операции;

♦ восьмеричное число содержит цифру 8 или 9;

♦ недопустимое применение регистра (например, переход к регистру);

♦ отсутствует оператор END.

Программисты весьма изобретательны по части новых ошибок. Ошибки с неопределенным символом часто являются следствием опечаток. Хороший ассемблер может просчитать, какой из всех определенных символов в большей степени соответствует неопределенному, и подставить его вместо неопределенного символа. Для исправления других ошибок ничего кардинального предложить нельзя. Лучшее, что может сделать ассемблер при обнаружении оператора с ошибкой, - вывести сообщение об ошибке на экран и попробовать продолжить ассемблирование.

Первый проход || Оглавление || Таблица символов