Алгоритм работы Макропроцессора
Очевидно, что когда Макропроцессор обрабатывает макровызов, он уже должен «знать» макроопределение данной макрокоманды. Для обеспечения этого таблицы макроопределений и имен макроопределений должны быть созданы до начала обработки макровызовов. Поэтому Макропроцессор должен состоять из двух проходов, на первом проходе строятся таблицы макроопределений и имен макроопределений, а на втором осуществляется обработка макровызовов. Если макроопределения сосредоточены в начале исходного модуля, то Макропроцессор может быть и однопроходным. Ниже мы приводим алгоритм работы 2-проходного Макропроцессора, при этом мы исходим из следующих предпосылок:
u наш Макропроцессор является независимым от Ассемблера;
u таблица параметров объединяется с таблицей локальных переменных, в дальнейшем мы называем объединенную таблицу таблицей локальных переменных;
u операторы Макроязыка включают в себя: MACRO, MEND, MEXIT, MNOTE, LOCL, GLBL, SET, MGO, MIF;
u обеспечиваются локальные и глобальные переменные макроопределений, уникальные метки.
Алгоритм выполнения 1-го прохода следующий:
F Блок1:1-й проход Макропроцессора
F Блок2:Инициализация: открытие исходного файла, создание пустых таблиц, признак «обработка макроопределения» устанавливается в FALSE.
F Блок3:Чтение следующей строки исходного файла с проверкой конца файла.
F Блок4:Если при чтении строки найден конец файла, выводится сообщение об ошибке, закрываются файлы, освобождается память...
F Блок5:...и Макропроцессор завершается с признаком ошибки.
F Блок6:Если конец файла не достигнут, выполняется лексический разбор прочитанной строки с выделением имени и мнемоники операции.
F Блок7:Алгоритм Макропроцессора разветвляется в зависимости от мнемоники операции
F Блок8:Если мнемоника операции MACRO — заголовок макроопределения, то в таблицу имен макроопределений заносится имя, находящееся в этом операторе и начальный адрес свободной области в таблице макроопределений. (При занесении имени в таблицу имен макроопределений проверяется, нет ли уже в таблице такого имени, если есть — ошибка)
F Блок9:Оператор MACRO записывается в таблицу макроопределений.
F Блок10:Признак «обработка макроопределения» устанавливается в TRUE.
F Блок11:Если мнемоника операции MEND — конец макроопределения, то оператор записывается в таблицу макроопределений...
F Блок12:...и признак «обработка макроопределения» устанавливается в FALSE.
F Блок13:Если мнемоника операции END — конец программы, то проверяется установка признака «обработка макроопределения».
F Блок14:Если этот признак установлен в TRUE, т.е., конец программы встретился до окончания макроопределения, то выводится сообщение об ошибке, закрываются файлы, освобождается память...
F Блок15:...и Макропроцессор завершается с признаком ошибки.
F Блок16:Если этот признак установлен в FALSE, то выполняются завершающие операции ...
F Блок17:...и заканчивается 1-й проход Макропроцессора.
F Блок18:При любой другой мнемонике оператора проверяется установка признака «обработка макроопределения».
F Блок19:Если этот признак установлен в TRUE, то оператор записывается в таблицу макроопределений, если признак установлен в FALSE, то оператор игнорируется Макропроцессором.
Алгоритм выполнения 2-го прохода следующий:
F Блок1:2-й проход макропроцессора
F Блок2:Начальные установки: открытие файлов, создание пустых таблиц. Признак режима обработки устанавливается в значение «обработка программы».
F Блок3:Признак конца обработки установлен?
F Блок4:Если признак конца обработки установлен, выполняются завершающие операции...
F Блок5:...и работа Макропроцессора заканчивается.
F Блок6:Выполняется разбор строки.
F Блок7:Проверяется признак режима обработки.
F Блок8:Если признак режима установлен в значение «обработка макроопределения», то проверяется мнемоника оператора.
F Блок9:Если в режиме обработки макроопределения встречается мнемоника MEND, то режим переключается в «обработка программы», все прочие операторы в режиме обработки макроопределения игнорируются.
F Блок10:Если признак режима работы установлен в значение «обработка программы», происходит ветвление алгоритма в зависимости от мнемоники оператора.
F Блок11:Обработка оператора MACRO заключается в установке режима обработки в значение «обработка программы».
F Блок12:Обработка директивы Ассемблера END заключается в установке признака окончания работы и выводе оператора в выходной файл.
F Блок13:Любая другая мнемоника ищется в Таблице машинных команд и в Таблице директив Ассемблера. Если мнемоника найдена в одной из этих таблиц, то...
F Блок14:...оператор просто выводится в выходной файл.
F Блок15:Если оператор не является оператором языка Ассемблера, то предполагается, что это макровызов и соответствующее мнемонике имя ищется в Таблице имен макроопределений.
F Блок16:Если имя не найдено в Таблице имен макроопределений, то оно ищется в библиотеках макроопределений.
F Блок17:Если имя не найдено и в библиотеках макроопределений, вырабатывается сообщение об ошибке и управление передается на чтение следующего оператора программы.
F Блок18:Если имя не найдено в библиотеках макроопределений, соответствующие элементы включаются в Таблицу имен макроопределений и в Таблицу макроопределений.
F Блок19:Если имя есть в Таблице макроопределений, выполняется обработка макровызова, после чего управление передается на чтение следующего оператора программы.
Алгоритм обработки макровызова следующий:
F Блок1:Обработка макровызова. На входе этого модуля есть номер элемента в Таблице имен макроопределений и разобранный текст оператора макровызова.
F Блок2:Создание пустых: Таблицы локальных переменных, Таблицы меток.
F Блок3:Чтение первой строки из Таблицы макроопределений по адресу, записанному в элементе Таблице имен макроопределений. (Здесь и далее мы подразумеваем, что после чтения очередной строки макроопределения указатель для следующего чтения устанавливается на адрес следующей строки, если он не изменен явным образом.)
F Блок4:Проверка параметров: сопоставление фактических параметров вызова с формальными параметрами, описанными в заголовке макроопределения (Заголовок находится в строке, только что считанной из Таблицы макроопределений).
F Блок5:При несоответствии фактических параметров формальным выдается сообщение об ошибке...
F Блок6:...и обработка макровызова завершается
F Блок7:При правильном задании фактических параметров параметры и их значения заносятся в Таблицу локальных переменных.
F Блок8:Создается и заполняется Таблица меток макроопределения. При этом текст макроопределения просматривается до оператора MEND, выявляются метки и заносятся в таблицу. Проверяется уникальность меток. После заполнения таблицы меток указатель чтения из Таблицы макроопределений устанавливается на вторую (следующую за заголовком строку) текста макроопределения.
F Блок9:Читается следующая строка текста макроопределения.
F Блок10:Если строка является комментарием Ассемблера, строка выводится в макрорасширение.
F Блок11:Если строка является комментарием Макроязыка, управление передается на чтение следующей строки макроопределения.
F Блок12:Выполняется разбор строки.
F Блок13:Алгоритм ветвится в зависимости от мнемоники оператора.
F Блок14:При обработке оператора LOCL имя локальной переменной ищется сначала в Таблице локальных переменных...
F Блок15:...а затем — в Таблице глобальных переменных.
F Блок16:Если имя найдено в одной из таблиц, формируется сообщение о неуникальном имени.
F Блок17:В противном случае заносится новая строка в таблицу локальных имен. В любом случае управление передается на чтение следующей строки макроопределения.
F Блок18:Обработка оператора GLBL отличается от оператора LOCL только тем, что новая строка создается в Таблице глобальных переменных.
F Блок19:При обработке оператора LOCL вычисляется выражение — операнд команды. Вычисление включает в себя подстановку значений входящих в выражение переменных. Возможны ошибки — из-за использования неопределенных имен и ошибок в синтаксисе выражения.
F Блок20:Имя переменной ищется сначала в Таблице локальных переменных.
F Блок21:Если имя найдено, изменяется его значение в Таблице локальных переменных.
F Блок22:Если имя переменной не найдено, оно ищется в Таблице глобальных переменных.
F Блок23:Если имя найдено в Таблице глобальных переменных, изменяется его значение в этой таблице.
F Блок24:Если имя не найдено ни в одной из таблиц, формируется сообщение о неопределенном имени.
F Блок25:При обработке оператора MIF вычисляется условное выражение — 1-й операнд команды (возможны ошибки).
F Блок26:Проверяется значение вычисленного условного выражения.
F Блок27:Если значение выражения «истина», имя метки — 2-го операнда команды ищется в Таблице меток макроопределения.
F Блок28:Если метка найдена в таблице, указатель для следующего чтения из Таблице макроопределений устанавливается на адрес соответствующий метке
F Блок29:Если метка найдена в таблице, выдается сообщение о неопределенной метке.
F Блок30:При обработке оператора MGO имя метки — операнда команды ищется в Таблице меток макроопределения.
F Блок31:Если метка найдена в таблице, указатель для следующего чтения из Таблице макроопределений устанавливается на адрес соответствующий метке.
F Блок32:Если метка найдена в таблице, выдается сообщение о неопределенной метке.
F Блок33:При обработке оператора MNOTE выводится сообщение, определяемое операндом.
F Блок34:Устанавливается и анализируется код серьезности. Код серьезности является общим для всей работы Макропроцессора, его значение изменяется только, если новое значение больше текущего (более серьезная ошибка)
F Блок35:Если код серьезности не допускает продолжения работы Макропроцессора, устанавливается признак завершения работы.
F Блок36:При обработке оператора MEXIT устанавливается и анализируется код серьезности.
F Блок37:Если код серьезности не допускает продолжения работы Макропроцессора, устанавливается признак завершения работы.
F Блок38:Освобождаются структуры данных, созданные для обработки макровызова...
F Блок39:...и обработка макровызова завершается.
F Блок40:При обработке оператора MEND освобождаются структуры данных, созданные для обработки макровызова...
F Блок41:...и обработка макровызова завершается.
F Блок42:Любая другая мнемоника операции означает, что оператор является не оператором Макроязыка, а оператором языка Ассемблера. В этом случае прежде всего проверяется, не имеет ли оператор метки, которая должна быть уникальной.
F Блок43:Если оператор имеет такую метку, формируется имя уникальной метки и индекс уникальных меток увеличивается на 1.
F Блок44:Выполняются подстановки в операторе языка Ассемблера (значение имен ищутся в Таблицах локальных и глобальных переменных, возможны ошибки).
F Блок45:Оператор языка Ассемблера записывается в макрорасширение.
Библиотеки макроопределений
Макровызовы к макроопределение, приведенному в исходном модуле, могут применяться только в этом же исходном модуле. Для того, чтобы можно было использовать макроопределение в разных исходных модулях, макроопределения помещаются в библиотеку макроопределений. Список библиотек макроопределений, которые используются для данного исходного модуля является параметром Макропроцессора.
Мы в нашей схеме алгоритма показали, что обращение к библиотекам макроопределений происходит на 2-м проходе Макропроцессора — если мнемоника оператора не распознана ни как оператор языка Ассемблера, ни как макрокоманда, определенная в данном исходном модуле. Возможны, однако, и другие алгоритмы использования библиотек.
Один из таких алгоритмов следующий.
Анализ мнемоники производится на 1-м проходе Ассемблера, все операторы, не распознанные как операторы языка Ассемблера, считаются макрокомандами и для них создаются строки в Таблице имен макроопределений.
Если для такой макрокоманды макроопределение еще не найдено, поле ссылки на Таблицу макроопределений остается пустым.
Если в исходном модуле встречается макроопределение, то его текст заносится в Таблицу макроопределений. Если в Таблице имен макроопределений уже есть это имя с пустой ссылкой на Таблицу макроопределений, ссылке присваивается значение. Если такого имени в Таблице имен макроопределений нет, в таблице создается новая строка.
В конце 1-го прохода просматривается Таблица имен макроопределений. Если в таблице находятся имена с пустыми ссылками на Таблицу макроопределений, соответствующее макроопределение ищется в библиотеках. Если макроопределение найдено в библиотеке, его текст переписывается в Таблицу макроопределений и присваивается значение ссылке в соответствующей строке Таблицы имен макроопределений.
Если после этого в Таблице имен макроопределений остаются имена с пустыми ссылками, это свидетельствует об ошибках в программе.
Вложенные макровызовы.
Вложенные макроопределения
Можно ли употреблять макроопределения внутри макроопределений? Можно ли употреблять макровызовы вызовы внутри макроопределений? Представленные выше алгоритмы делать этого не позволяют. Тем не менее, можно построить такие алгоритмы Макропроцессора, которые это позволять будут. Эти алгоритмы в любом случае будут довольно «затратными», то есть, требующими много ресурсов — процессорного времени и памяти. «Классические» алгоритмы, создававшиеся в условиях хронического дефицита памяти, были очень ограничены.
Макроопределения внутри макроопределений
Честно говоря, необходимость в таких средствах сомнительна. Она может возникнуть при создании большого макроопределения, в котором есть повторяющиеся фрагменты. Вложенное макроопределение действительно только внутри того макроопределения, в которое оно вложено.
Против такого средства можно привести 2 соображения:
u макроопределение не бывает слишком большим — иначе не срабатывают его преимущества над подпрограммой (следует однако признать, что могут существовать довольно большие макроопределения, которые генерируют разнообразные варианты небольших макрорасширений);
u в языке Pascal допускаются вложенные процедуры, а в языке C — нет; и C прекрасно обходится без них, да и современная практика программирования на Pascal их практически не использует.
Тем не менее, если вложенные макроопределения все же необходимы, можно предложить следующий вариант их реализации: 1-й проход Макропроцессора работает почти по тому же алгоритму, который приведен нами. Принципиально важно, однако, что Таблица макроопределений и Таблица имен макроопределений имеют последовательную структуру, элементы в них записываются в порядке их поступления.
В Макропроцессоре есть некоторая целая переменная — глубина вложенности. Ее исходное значение — 0, при каждом появлении оператора MACRO это значение увеличивается на 1, при каждом появлении оператора MEND — уменьшается на 1. Если при глубине вложенности 0 появляется оператор MACRO, в Таблицу имен макроопределений заносится новый элемент, и текст макроопределения записывается в Таблицу макроопределений — до тех пор, пока глубина вложенности не станет равной 0.
Появление оператора MACRO при глубине вложенности, большей 0 не приводит к созданию нового элемента в Таблице имен макроопределений.
Таким образом, в Таблице имен макроопределений имеется строка только для самого внешнего макроопределения, а все вложенные пока «не видны» и находятся внутри текста внешнего в Таблице макроопределений.
2-й проход Макропроцессора при обработке макровызова считывает текст макроопределения в некоторый буфер и прежде всего рекурсивно вызывает для его обработки Макропроцессор.
Для вложенного вызова Макропроцессора доступны Таблица макроопределений и Таблица имен макроопределений, новые макроопределения, обнаруженные рекурсивным вызовом заносятся в конец этих таблиц.
При возврате из рекурсивного вызова макроопределения, дописанные им, удаляются из таблиц.