Ассемблер. Компоновщик. Загрузчик. Макрогенератор (1108377), страница 7
Текст из файла (страница 7)
│ X | │ │ X | 1 │ │ X | 2 │
│-------│ │-------│ │-------│
│ <1,2> │ │ <2> │ │ <> │
│-------│ │-------│ │-------│
│ │ │ 103 │ │ 103 │ │ 103 │
│--------│ │=======│ │=======│ │=======│ │-------│
│ 3 -> 2 │ │ 2 │ │ 2 │ │ 2 │ │ 2 │
│--------│ │-------│ │-------│ │-------│ │-------│
│ 101 │ │ 101 │ │ 101 │ │ 101 │ │ 101 │
│========│ │=======│ │=======│ │=======│ │=======│
│////////│ │///////│ │///////│ │///////│ │///////│
НС=100 НС=102 НС=103 НС=103 НС=105
а) б) в) г) д)
В нашем примере все это происходит так. Поскольку счетчик в стеке (3) не равен 0, то он уменьшается на 1 [заменить 3 на 2], и НС получает значение 101. В 101-й строке находится обычное предложение ЯА, поэтому макрогенератор сразу передает его на обработку ассемблеру, т.е. это предложение попадает в окончательный текст программы [записать], после чего управление возвращается макрогенератору, который увеличивает НС на 1.
Итак, НС=102. В 102-й строке находится директива макроязыка, с нее начинается блок повторения, но уже типа IRP. Напомню, что по IRP-блоку создается столько копий тела блока, сколько фактических параметров перечислено в уголках, причем в i-й копии формальный параметр (X) должен заменяться на i-й фактический параметр. Чтобы можно было построить такие копии, макрогенератор должен знать следующие вещи: номер первой строки тела блока, список еще не просмотренных фактических параметров и то, на какой фактический параметр надо заменять в текущей копии формальный параметр. С записи этой информации в стек макрогенератор и начинает обработку IRP-блока (рис. б): начало тела - это текущее значение НС плюс 1, список параметров берется из директивы IRP; кроме того, в стек записывается формальный параметр (X), но пока без пары.
Дальнейшие действия макрогенератора по обработке IRP-блока идут в следующем цикле: если список параметров в стеке не пуст, тогда от списка отщепляется первый параметр и он ставится в соответствие формальному параметру, после чего НС устанавливается на начало тела (рис. в) и начинается просмотр тела блока. Когда будет достигнут конец тела, то эти действия повторяются заново, и так до тех пор, пока список параметров не станет пустым.
В нашем примере происходит следующее. Поскольку список фактических параметров <1,2> не пуст, то от него отщепляется первый параметр 1 и он ставится в соответствие формальному параметру X. Затем НС устанавливается на начало тела: НС=103, т.е. начинается обработка 103-й строки. В ней прежде всего заменяются все вхождения формального параметра X на текущий фактический параметр 1:
DB X --> DB 1
Далее определяется тип полученного предложения. Т.к. у нас это обычное предложение ЯА, то макрогенератор ничего с ним не делает, а передает его на обработку ассемблеру, т.е. это предложение записывается в окончательный вариант программы [записать]. После этого НС увеличивается на 1. Теперь НС=104. Это директива ENDM, оканчивающая тело блока повторения. Макрогенератор делает следующее (рис. г): поскольку у нас еще остались параметры, то из их списка удаляется первый параметр (2) и он ставится в соответствие формальному параметру, после чего НС снова устанавливается на начало тела блока (НС=103) и снова начинается просмотр тела блока.
При этом просмотре в окончательный вариант программы будет записано предложение DB 2 [записать], после чего НС станет равным 104 и снова будет достигнута директива ENDM. Т.к. на этот раз список параметров уже пуст, то обработка IPR-блока завершается: из стека удаляется вся информация, относящаяся в этому блоку, и НС увеличивается на 1 (рис. д), т.е. макрогенератор идет по тексту за блок.
Теперь НС=105. Снова директива ENDM, но уже относящаяся к блоку REPT. (Отмечу, что макрогенератор помимо той информации, о которой я сказал, заносит в стек пометки, по которым можно определить, к какому типу блока повторения относится информация в вершине стека, поэтому макрогенератор знает, какого типа блок только что завершился.) О действиях макрогенератора в данном случае я уже говорил: проверив, что текущее значение (2) счетчика копий отлично от нуля, он уменьшает его на 1 и восстанавливает в НС номер (101) 1-й строки тела блока. Начинается новый просмотр тела блока. И так далее, пока счетчик не станет нулевым. Тогда происходит очистка стека, НС увеличивается на 1 (НС=106) и макрогенератор переходит к обработке строки, следующей за блоком повторения.
На этом я закончу рассказ про обработку блоков повторения.
5. ОБРАБОТКА IF-БЛОКОВ.
Напомню, что с помощью IF-блоков реализуется условное ассемблирование, т.е. возможность вставлять или не вставлять в окончательный текст программы какие-то фрагменты исходного текста.
IF-блоки имеют следующий вид:
<IF-директива>
<фрагмент-1>
ELSE
<фрагмент-2>
ENDIF
причем часть ELSE может отсутствовать. Смысл этой конструкции следующий: если условие в IF-директиве выполнено, тогда в окончательный текст программы попадает фрагмент-1 и не попадает фрагмент-2, а если условие не выполнено, то, наоборот, в окончательный текст программы не попадает фрагмент-1, а попадает фрагмент-2 (если части ELSE нет, то в последнем случае IF-блок ничего не поставляет в окончательный текст программы).
Действия макрогенератора при обработке IF-блоков очевидны. Встретив какую-то из IF-директив (например, IF или IFIDN), макрогенератор проверяет ее условие. Если оно выполнено, то макрогенератор передает ассемблеру все строки до директивы ELSE; саму же директиву ELSE и все последующие строки до ENDIF макрогенератор пропускает. Если же условие не выполнено, тогда макрогенератор пропускает все строки до ELSE и только затем начинает передавать ассемблеру строки между ELSE и ENDIF.
Единственная, пожалуй, проблема, которая возникает при обработке IF-блоков, связана с вложенностью IF-блоков. Рассмотрим такой пример:
IF ...
...
IFIDN ...
...
ELSE
...
ENDIF
...
ELSE
...
ENDIF
Если условие в директиве IF не выполнено, то макрогенератор должен пропустить все строки до директивы ELSE, но не до первой встретившейся, а до "своей", т.е. он должен проигнорировать все вложенные IF-блоки.
Эта проблема аналогична задаче проверки произвольного текста на сбалансированность по круглым скобкам, и решается она аналогично. Поскольку на семинарах задачу со скобками вы решали, то укажу лишь идею решения. Вводится счетчик, указывающий уровень вложенности IF-блоков: при появлении IF-директивы этот счетчик увеличивается на 1, а при появлении директивы ENDIF, он уменьшается на 1. С помощью такого счетчика макрогенератор и отыскивает нужную директиву ELSE - это директива, для которой счетчик имеет то же значение, что и для исходной IF-директивы.
Ничего более про обработку IF-блоков я говорить не буду и на этом закончу рассказ про макрогенератор.
В заключении я хочу обратить ваше внимание на одну важную вещь. Как вы видите, макрогенератор, ассемблер и другие подобные программы активно пользуются таблицами, причем эти таблицы могут быть очень большими: например, в большой программе на ЯА таблица имен, создаваемая ассемблером, может содержать сотни и тысячи имен. Ясно, что если мы хотим, чтобы эти программы работали быстро, то такие таблицы должны быть организованы не кое-как, а с умом - так, чтобы поиск в них велся как можно быстрее.
Так вот, для этого используются те способы организации таблиц, о которых вам рассказывали в первом семестре. Например, ассемблер пользуется таблицей мнемокодов, в которой перечислены символьные названия всех машинных команд. Эта таблица создается только один раз (вместе с самим ассемблером), поэтому она не меняется, а используется только для поиска. В связи с этим такую таблицу можно организовать в виде упорядоченной таблицы или в виде перемешанной таблицы с заранее подобранной хорошей функцией расстановки. С другой стороны, таблица имен создается в процессе трансляции исходной программы, и в отношении ее активно применяется как операция вставки, так и операция поиска. Ясно, что здесь уже нельзя использовать упорядоченную таблицу, т.к. она плохо приспособлена для добавления новых элементов, а надо использовать перемешанную таблицу или таблицу в виде двоичного дерева, т.к. в этих случаях будет обеспечен и быстрый поиск, и быстрые вставки.
Учтите все это, если вам в будущем придется создавать ассемблеры или трансляторы вообще.