МАКРОГЕНЕРАТОР.
Возможны два варианта взаимодействия макрогенератора (МГ) с ассемблером.
В первом варианте МГ работает до ассемблера и полностью независим от него: МГ вводит текст программы на макроязыке и преобразует его, получая новый текст на "чистом" языке ассемблера (ЯА), и только затем начинает работать ассемблер. В этом случае МГ выступает в роли т.н. препроцессора (препроцессором называют вспомогательную программу, работающую до основной программы и преобразующую исходный текст к виду, удобному для работы основной программы).
Достоинством этого варианта является то, что так легче понять сам макроязык и работу МГ, так легче реализовать МГ и ассемблер. Однако у этого варианта имеются недостатки. Во-первых, приходится дважды просматривать текст программы (а это потери времени), а во-вторых, и это главное, при таком взаимодействии МГ не может использовать информацию, извлекаемую ассемблером из программы. Поясним это на примере.
Пусть программа имеет такой вид:
N EQU 1
...
IF N EQ 1
...
Директива EQU не относится к макроязыку, поэтому МГ не должен ее обрабатывать (это задача ассемблера) и потому он не узнает, что N
обозначает константу со значением 1. Директива же IF относится к макроязыку, поэтому МГ должен ее обрабатывать, в частности должен сравнить N с 1, но сделать это он не может, т.к. не знает, что обозначает имя N.
Этот пример показывает, что если МГ работает независимо от ассемблера, то либо надо запретить использование в директивах макроязыка констант и других объектов, смысл которых становится известным позже, при работе ассемблера, либо надо заставить МГ хотя бы частично выполнять работу ассемблера (скажем, обрабатывать директивы EQU). Ясно, что оба этих требования не очень хорошие.
Отмеченные недостатки устраняются при втором варианте взаимодействия МГ с ассемблером - когда текст программы просматривается только раз, но его обрабатывают одновременно (а точнее, чередуясь) и МГ, и ассемблер. Делается это так. Очередное предложение программы сначала просматривает МГ. Если это обычное предложение ЯА (например, директива N EQU 1), тогда МГ ничего с ним не делает, а сразу передает его на обработку ассемблеру. Ассемблер же, обработав это предложение (у нас - записав в таблицу имен, что N - это имя константы со значением 1), возвращает управление МГ, который переходит к следующему предложению программы. Если же очередное предложение программы - это конструкция макроязыка (например, IF N EQ 1), тогда его обработкой занимается сам МГ. В таких случаях МГ либо ничего не сообщает ассемблеру об этом предложении (как в случае директивы IF), либо (если это макрокоманда) генерирует несколько обычных предложений, которые по одному передает на обработку ассемблеру, и только после этого переходит к следующему предложению программы. Ясно, что в данном случае МГ может пользоваться информацией, извлеченной ассемблером из программы; например, в нашем случае МГ может забраться в таблицу имен и узнать, что означает имя N.
Этот второй вариант взаимодействия макрогенератора с ассемблером можно условно изобразить так:
программа на -----¬ строка ----------¬
макроязыке --> ¦ МГ ¦ -------> ¦ассемблер¦ --> маш.программа
L----- на ЯА L----------
L----<-------------
Здесь МГ выступает, с точки зрения ассемблера, в роли процедуры ввода строки. Обе эти программы можно рассматривать как части одной программы, которую принято называть макроассемблером.
Именно второй вариант используется в системе MASM, именно его мы и будем придерживаться в рассказе про работу МГ.