Flat Assembler. Мануал программера.
2.3 Директивы препроцессора.
Все директивы препроцессора выполняются перед основным ассемблированием, и таким образом директивы управления на них никак не влияют. В это время также удаляются все комментарии.
2.3.1 Включение файлов-исходников.
« include» включает указанный файл-исходник туда, где эта директива используется. За ней должно следовать в кавычках имя файла, который должен быть включен, например:
Include 'macros.inc'.
Весь включенный файл обрабатывается препроцессором перед обработкой строк, следующих за содержащей директиву « include». Нет предела для количества включаемых файлов, пока они умещаются в память.
Путь, заключенный в скобки, может содержать окружающие переменные, заключенные в знаки « %», они будут заменены на их значения внутри пути. Знаки « \» и « /» трактуются как разделители пути. Если не указан абсолютный путь, сначала файл ищется в директории, содержащей файл, в который он включается, и, далее, если его там нет, в директории, содержащей главный файл-исходник (указанный в командной строке). Эти правила так же относятся к путям, которые указываются в директиве « file».
2.3.2 Символьные константы.
Символьные константы отличаются от числовых констант тем, что перед процессом ассемблирования они заменяются на их значения во всех строках кода, следующих за их определением, и все может стать их значением.
Определение символьных констант состоит из имени константы, за которой следует директива « equ». Все, что следует за этой директивой, станет значением константы. Если значение символьной константы содержит другие символьные константы, они заменяются на их значения перед присвоением значения новой константе. Например:
D equ dword.
NULL equ d 0.
D equ edx.
После этих трех определений значение « NULL» будет « dword 0», а значение « d» будет « edx». Так, например, « push NULL» будет сассемблировано как « push dword 0», а « push d» как « push edx». А, например, в такой строке:
D equ d,eax.
Константе « d» будет присвоено новое значение « edx,eax». Таким образом могут определяться растущие списки символов.
« restore» позволяет присвоить назад предыдущее значение переопределенной константы. За ней должно следовать одно или больше имен символьных констант, разделенных запятыми. Так, « restore d» после предыдущего переопределения вернет константе значение « edx», следующее применение этой директивы вернет ей значение « dword», а ещё одно применение восстановит первоначальное значение, как будто такая константа не определялась. Если не константа с заданным именем не определена, то « restore» не вызовет ошибку, а будет просто проигнорирована.
Символьные константы могут использоваться для адаптации синтаксиса ассемблера к персональным предпочтениям. Например, следующие определения создают удобные ярлыки для всех операторов размера:
B equ byte.
W equ word.
D equ dword.
P equ pword.
F equ fword.
Q equ qword.
T equ tword.
X equ dqword.
Так как символьная константа может так же иметь пустое значение, она может использоваться для того, чтобы допустить синтаксис со словом « offset» перед каким-нибудь значением адреса:
Offset equ.
После такого определения « mov ax, offset char» будет правильной конструкцией, которая будет копировать смещение переменной « char» в регистр « ax», так как « offset» заменяется пустым значением, и поэтому игнорируется.
Символьные константы могут также быть определены директивой « fix», которая имеет такой же синтаксис, как « equ», но определяет константы высшего приоритета — они заменяются их символическим значением даже перед совершением директив препроцессора и макроинструкций. Исключением является сама директива « fix», которая имеет наивысший возможный приоритет, и поэтому допускает переопределение констант, заданных таким путем. Но если такие константы высшего приоритета находятся внутри значения, следующего за директивой « fix», они заменяются их значениями перед присвоением этого значения новой константе.
Директива « fix» может использоваться для адаптирования директив препроцессора, что нельзя сделать директивой « equ». Например:
Incl fix include.
Определяет короткое имя для директивы « include», тогда как такое же определение директивой « equ» не даст такого результата, так как стандартные символьные константы заменяются на из значения после поиска строк с директивами препроцессора.
2.3.3 Макроинструкции.
« macro» позволяет вам определить собственный комплекс инструкций, называемых макроинструкциями. Их использование может существенно упростить процесс программирования. В своей простейшей форме директива похожа на описание символьной константы. Например, следующая строка определяет ярлык для инструкции « test al,0xFF»:
Macro tst {test al,0xFF}.
После директивы « macro» должно идти имя макроинструкции и далее её содержание, заключенное между знаками « {» и « }». Вы можете использовать инструкцию « tst» в любом месте после её определения и она будет ассемблирована как « test al,0xFF». Определение символьной константы с таким значением даст похожий результат, различие лишь в том, что имя макроинструкции будет распознаваться только как мнемоник инструкции. Также, макроинструкции заменяются соответствующим кодом даже перед заменой символьных констант на их значения. То есть, если вы вы определите макроинструкцию и символьную константу под одним и тем же именем и используете это имя как мнемоник инструкции, оно будет заменено на содержание макроинструкции, но если вы используете его внутри операндов, имя будет заменено на значение символьной константы.
Определение макроинструкции может сотоять из нескольких строк, потому что знаки « {» и « }» не обязательно должны находиться на одной строке директивой « macro». Например:
Macro stos0.
{
Xor al,al.
Stosb.
}
Макроинструкция « stos0» будет заменена на эти две инструкции ассемблера, где бы он не использовался.
Как и инструкции, которым требуются несколько операндов, для макроинструкции можно задать требование нескольких аргументов, разделяя их запятыми. Имена этих аргументов должны следовать за именем макроинструкции на строке с директивой « macro». В любом месте в макроинструкции, где эти имена появятся, они будут заменены соостветствующими значениями, указанными там, где макроинструкция используется. Вот пример макроинструкции, которая делает выравнивание данных для двоичного формата вывода:
Macro align value { rb (value-1)-($+value-1) mod value }.
Когда инструкция « align 4» встречается после этого задания макроинструкции, она заменяется на его содержание, и здесь « value» станет 4, а результат будет « rb (4–1)-($+4–1) mod 4».
Если в определении макроинструкции встречается её же имя, то используется предыдущее значение этого имени. Таким образом могут быть сделаны полезные переопределения макросинструкций, например:
Macro mov op1,op2.
{
If op1 in.
& op2 in.
Push op2.
Pop op1.
Else.
Mov op1,op2.
End if.
}
Эта макроинструкция расширяет синтаксис инструкции « mov», позволяя обоим операндам быть сегментными регистрами. Например, « mov ds,es» будет ассемблировано как « push es» и « pop ds». Во всех других случаях будет использована стандартная инструкция « mov». Синтаксис этого « mov» может быть расширен далее определением следующей макроинструкции с таким именем, который будет использовать предыдущий:
Macro mov op1,op2,op3.
{
If op3 eq.
Mov op1,op2.
Else.
Mov op1,op2.
Mov op2,op3.
End if.
}
Это позволяет инструкции « mov» иметь три операнда, но она так же все ещё может иметь два операнда, так как если макроинструкции задается меньше аргументов, чем ему требуется, оставшиеся заполняются пустыми значениями. Если заданы три операнда, то макроинструкция превратится в две ранее определенных, то есть « mov es,ds,dx» будет ассемблировано как « push ds», « pop es» и « mov ds,dx».
Если требуется создать макроинструкцию с аргументом, который содержит запятые, этот аргумент следует заключить между « <» и « >». Если он содержит больше одного знака « <», то для окончания его описания должно быть использовано такое же количество « >».
« purge» позволяет отменить последнее определение указанной макроинструкции. За директивой должно следовать одно или больше имен макроинструкций, разделенных запятыми. Если указанная макроинструкция не определена, это не вызовет ошибку. Например, после расширения синтаксиса « mov» вышеуказанными макроинструкциями вы можете отключить синтаксис с тремя операндами, используя директиву « purge mov». Следующее « purge mov» отключит синтаксис для сегментных регистров, а дальнейшее применение этой директивы не возымеет эффекта.
Если после директивы « macro» вы заключаете некоторую группу аргументов в квадратные скобки, это позволит при использовании макроинструкции задать данной группе аргументов больше значений. Любой следующий аргумент данный после последнего аргумента данной группы начнет новую группу и станет её первым членом. Поэтому после закрытия квадратных скобок не должно быть имен аргументов. Содержание макроинструкции будет обрабатываться для каждой такой группы аргументов отдельно. Простейший пример — это заключение одного имени аргумента в квадратные скобки:
Macro stoschar [char].
{
Mov al,char.
Stosb.
}
Эта макроинструкция допускает неограниченное число аргументов, и каждый будет обработан этими двумя инструкциями отдельно. Например, « stoschar 1,2,3» будет ассемблирован как следующие инструкции:
Mov al,1.
Stosb.
Mov al,2.
Stosb.
Mov al,3.
Stosb.
Существуют некоторые специальные директивы, возможные только внутри определений макроинструкций. Директива « local» задает локальные имена, которые будут заменены уникальными значениями каждый раз, когда используется макроинструкция. За ней должны следовать имена, разделенные запятыми. Эта директива обычно требуется для внутренних констант или меток макроинструкции. Например:
Macro movstr.
{
Local move.
Move:
Lodsb.
Stosb.
Test al,al.
Jnz move.
}
Каждый раз, когда используется эта макроинструкция, « move» заменяется новым уникальным именем. То есть вы не получите ошибку, это обычный случай, когда метка определяется больше, чем один раз.
« forward», « reverse» и « common» делят макроинструкцию на блоки, каждый из которых обрабатывается после окончания обработки предыдущего. Они различаются в поведении только если макроинструкция поддерживает много групп аргументов. Блок инструкций, следующий за « forward» будет обрабатываться для каждой группы аргументов от первой до последней, как блок по умолчанию (без этих директив). Блок, идущий за « reverse» будет обрабатываться для каждой группы аргументов в обратном порядке — от последней до первой. Блок за директивой « common» обрабатывается лишь один раз, просто для всех групп аргументов. Локальное имя, определенное в одном блоке, доступно во всех следующих блоках при обработке той же группы аргументов. Если оно было определено в блоке « common», оно доступно во всех следующих блоках, независимо от обрабатываемой группы.
Вот пример макроинструкции, которая создает таблицу адресов строк и следующих за ними строк.
Macro strtbl name,[string].
{
Common.
Label name dword.
Forward.
Local label.
Dd label.
Forward.
Label db string,0.
}
Первый аргумент, задаваемый этой макроинструкции, станет меткой для таблицы адресов, следующими аргументами должны быть строки. Первый блок обрабатывается однажды и определяет метку, второй блок назначает локальную метку для каждой строки и определяет запись в таблице, содержащий адрес этой строки. Третий блок определяет данные каждой строки с соответствующей меткой.
Первая инструкция, следующая за директивой, начинающей блок в макроинструкции, может идти с ней на той же строке, как на следующем примере:
Macro stdcall proc,[arg].
{
Reverse push arg.
Common call proc.
}
Это макрос может применяться для вызова процедур, используя соглашение STDCALL, аргументы сохраняются в стеке в обратном порядке. Например, « stdcall foo,1,2,3» будет ассемблировано так:
Push 3.
Push 2.
Push 1.
Call foo.
Если некоторое имя внутри макроинструкции имеет несколько значений (это либо один из аргументов, заключенных в квадратные скобки, либо локальное имя, определенное в блоке, следующем за директивой « forward» или « reverse») и используется в блоке, следующем за директивой « common», оно будет заменено на все значения, разделенные запятыми. Например, следующий макрос передать все дополнительные аргументы ранее определенной макроинструкции « stdcall»:
Macro invoke proc,[arg].
{ common stdcall [proc],arg }.
Он может применяться для непрямого вызова (через указатель в памяти) процедуры, используя соглашение STDCALL.
Внутри макроинструкции также может быть использован специальный оператор « #». Этот оператор сцепляет два имени в одно. Это может быть полезно, так как делается после того, как аргументы и локальные имена заменяются на свои значения. Следующая макроинструкция генерирует условный переход в зависимости от аргумента « cond»:
Macro jif op1,cond,op2,label.
{
Cmp op1,op2.
J#cond label.
}
Например, « jif ax,ae,10h,exit» будет ассемблировано как инструкции « cmp ax,10h» и « jae exit».
Оператор « #» может также использоваться для объединения двух строк, заключенных в кавычки.
Возможно преобразование имени в строку в кавычках с помощью оператора « `», который также может быть использован внутри макроинструкции. Он конвертирует следующее за ним имя в строку, заключенную в скобки, но имейте в виду, что если за ним следует аргумент, который заменяется на значение, содержащее больше, чем один символ, будет преобразован только первый из них, так как оператор « `» конвертирует только символ, идущий непосредственно за ним. Здесь пример использования этих двух свойств:
Macro label name.
{
Label name.
If ~ used name.
Display `name # "is defined but not used.",13,10.
End if.
}
Если метка, определенная таким макросом, не используется в коде, он известит вас об этом сообщением, указывающим, к какой метке это относится.
Чтобы создать макроинструкцию, ведущую себя по-разному в зависимости от типа аргументов, например если это строки в кавычках, вы можете использовать оператор сравнения « eqtype». Вот пример его использования для отделения строки в кавычках от других типов аргументов:
Macro message arg.
{
If arg eqtype "".
Local str.
Jmp @f.
Str db arg,0Dh,0Ah,24h.
@@:
Mov dx,str.
Else.
Mov dx,arg.
End if.
Mov ah,9.
Int 21h.
}
Вышеописанный макрос создан для показа сообщений в программах DOS. Если аргумент этого макроса некоторое число, метка или переменная, показывается строка из этого адреса, но если аргумент — это строка в кавычках, то созданный код покажет её после … и … .
Также возможно объявить макроинструкцию внутри другой макроинструкции, то есть один макрос может определить другой, но с такими определениями есть проблема, вызванная тем, что знак « }» не может появляться внутри макроинструкции, он всегда означает конец его определения. Чтобы обойти эту проблему, можно избавиться от мешающих символов. Это делается путем подстановки одного или больше обратных слэшей перед любыми другими символами (даже специальными знаками). Препроцессор видит эту последовательность как один символ, но каждый раз, когда он видит такой символ во время обработки макроса, он обрезает обратные слэши с его начала. Например, « \{» трактуется как один символ, но во время обработки макроса он станет символом « \{». Это позволит вам определить одну макроинструкцию внутри другой:
Macro ext instr.
{
Macro instr op1,op2,op3.
\{
If op3 eq.
Instr op1,op2.
Else.
Instr op1,op2.
Instr op2,op3.
End if.
\}.
}
Ext add.
Ext sub.
Макрос « ext» определен корректно, но когда он используется, символы « \{» и « \}» становятся « {» и « }». То есть когда обрабатывается « ext add», содержание макроса становится действительным определением макроинструкции, и таким образом определяется макрос « add». Так же « ext sub» определяет макрос « sub». Использование символа « \{» не было здесь действительно необходимо, но сделано таким образом для того, чтобы определение было более ясным.
Если некоторые директивы, специфические для макроинструкций, такие как « local» или « common», требуются в некотором макросе, включенном таким образом, то их можно избежать таким же путем. Исключение символа больше чем одним обратным слэшем так же поддерживается, это позволяет допустить множественные уровни вложения определений макросов.
Другая техника определения макроинструкций внутри других состоит в использовании директивы « fix», которая становится полезной, когда некоторый макрос только начинает определение другого, без его закрытия. Например:
Macro tmacro params.
{
Macro params {
}
MACRO fix tmacro.
ENDM fix }.
Определяет альтернативный синтаксис определения макросов, который выглядит как:
MACRO stoschar char.
Mov al,char.
Stosb.
ENDM.
Имейте в виду, что таким образом заданное определение должно быть создано с применением директивы « fix», так как перед тем, как процессор ищет знак « }» во время определения макроса, обрабатываются только символьные константы высшего приоритета! Может возникнуть проблема, если требуется выполнить некоторые дополнительные задания в конце такого определения, но есть еще одно свойство, которое в таких случаях поможет вам. А именно возможно поместить любую директиву, инструкцию или макроинструкцию сразу после символа « }», который заканчивает макроинструкцию и она будет обработана так же, как если бы была на следующей строке.
2.3.4 Структуры.
« struc» — это специальный вариант директивы « macro», который используется для определения структур данных. Макроинструкции, определенные директивой « struc», когда используются, должны предваряться меткой (как директивы определения данных). Эта метка будет также присоединена к началу каждого имени, начинающегося с точки, в содержании макроинструкции. Макроинструкция, определенная с использованием директивы « struc», может иметь такое же имя, как макросы, определенные с использованием директивы « macro». Структурная макроинструкция не будет мешать обычному макросу, выполняющемуся без метки перед ним и наоборот. Все правила и свойства, касающиеся стандартных макросов, применимы к структурным макроинструкциям.
Вот пример структуры:
Struc point x,y.
{
.x dw x.
.y dw y.
}
Например « my point 7,11» определит структуру, помеченную « my», содержащую две переменные: « my.x» со значением 7 и « my.y» со значением 11.
Еслигде-то в определении структуры находится имя, состоящие из одной лишь точки, оно заменяется на имя метки для данного примера структуры и эта метка таким образом не будет определена автоматически, позволяя полностью задать определение. Следующий пример использует это свойство, чтобы расширить определение директивы « db» с возможностью вычисления размера определяемых данных:
Struc db [data].
{
Common.
. db data.
.size = $ —.
}
Таким образом строка « msg db 'Hello!',13,10» определит так же константу « msg.size», равную размеру определяемых данных в байтах.
Определение структур данных, адресованных регистрами или абсолютными значениями может быть сделано структурными макроинструкциями с использованием директивы « virtual» (смотрите 2.2.3).
« restruc» удаляет последнее определение структуры, так же как « purge» делает с макросами и « restore» с символьными константами. Директива имеет тот же синтаксис — за ней должно следовать одно или несколько имен структурных макросов, разделенных запятыми.
2.3.5 Повторение макроинструкций.
Директива « rept» — это специальный вид макроинструкций, который делает заданное число дубликатов блока, заключенного в фигурные скобки. Простой синтаксис — число, следующее за « rept» (это не может быть выражение, так как препроцессор не совершает вычисления, если вам нужны повторения, базирующиеся на выражениях, вычисленных ассемблером, используйте одну из директив, обрабатываемых ассемблером, смотрите 2.2.2), и блок кода, заключенный между знаками « {» и « }». Простейший пример:
Rept 5 { in al,dx }.
Создает пять дубликатов строки « in al,dx». Блок инструкций определяется таким же образом, как для стандартных макросов, и допускаются все специальные операторы и директивы, которые могут использоваться только внутри макроинструкций. Если заданное число равно нулю, блок просто пропускается, как если бы вы определили макрос, но не использовали его. За количеством повторений может следовать имя символа-счетчика, который символьно будет заменяться на номер текущего повторения. Таким образом:
Rept 3 counter.
{
Byte#counter db counter.
}
Сгенерирует строки:
Byte1 db 1.
Byte2 db 2.
Byte3 db 3.
Механизм повторения, применяемый к блокам « rept» такой же, как тот, что используется для обработки множественных групп аргументов макросов, то есть директивы, такие как « forward», « common» и « reverse» могут использоваться их обычном значении.
Итак, такой макрос:
Rept 7 num { reverse display `num }.
Покажет символы от 7 до 1 как текст. Директива « local» работает так же, как внутри макросов с несколькими группами аргументов, то есть:
Rept 21.
{
Local label.
Label: loop label.
}
Сгенерирует уникальную метку для каждого дубликата. Символ-счетчик обычно начинает с 1, но вы можете объявить другое базовое значение, предваренное запятой, сразу же после имени счетчика. Например:
Rept 8 n:0 { pxor xmm#n,xmm#n }.
Сгенерирует код, очищающий содержимое регистров SSE. Вы можете определить несколько счетчиков, разделенных запятыми, и каждый может иметь свою базу.
« irp» итерирует один аргумент через данный список параметров. Синтаксис такой: за « irp» следует имя аргумента, далее запятая и далее список параметров. Параметры определяются таким же образом, как в вызове стандартного макроса, то есть они должны разделяться запятыми и каждый может быть заключен между знаками «<» и «>». Так же за именем аргумента может следовать « *» для обозначения того, что он не может иметь пустое значение. Такой блок:
Irp value, 2,3,5.
{ db value }.
Сгенерирует строки:
Db 2.
Db 3.
Db 5.
« irps» итерирует через данный список символов, за директивой должно следовать имя аргумента, далее запятая и далее последовательность любых символов. Каждый символ в последовательности, независимо от того, символы ли это имен, знаки символов или строки в кавычках, становится значением аргумента на одну итерацию. Если за запятой никаких символов не следует, то итераций не производится вообще. Этот пример:
Irps reg, al bx ecx.
{ xor reg,reg }.
Сгенерирует строки:
Xor al,al.
Xor bx,bx.
Xor ecx,ecx.
Блоки, определенные директивами « irp» и « irps», обрабатываются так же, как макросы, то есть операнды и директивы, специфичные для макросов могут в них свободно использоваться.
2.3.6 Условный препроцессинг.
При применении директивы « match» некоторый блок кода обрабатывается препроцессором и передаётся ассемблеру, только если заданная последовательность символов совпадает с образцом. Образец идет первым, заканчивается запятой, далее идут символы, которые должны подходить под образец, и далее блок кода, заключенный в фигурные скобки, как макроинструкция.
Есть несколько правил для построения выражения для сравнения, первое — это любые символьные знаки и строки в кавычках должны соответствовать абсолютно точно. В этом примере:
Match +,+ { include 'first.inc' }.
Match +,- { include 'second.inc' }.
Первый файл будет включен, так как « +» после запятой соответствует « +» в образце, а второй файл не будет включен, так как совпадения нет.
Чтобы соответствовать любому другому символу буквально, он должен предварятья знаком « =» в образце. Также чтобы привести в соответствие сам знак « =», или запятую должны использоваться конструкции « ==» и « =,». Например, образец « =a==» будет соответствовать последовательности « a=».
Если в образце стоит некоторый символ имени, он соответствует любой последовательности, содержащей по крайней мере один символ и его имя заменяется на поставленную в соответствие последовательность везде в следующем блоке, аналогично параметрам в макроинструкции. Например:
Match a-b, 0–7.
{ dw a,b-a }.
Сгенерирует инструкцию « dw 0, 7–0». Каждое имя всегда ставится в соответствие как можно меньшему количеству символов, оставляя оставшиеся, то есть:
Match a b, 1+2+3 { db a }.
Имя « a» будет соответствовать символу « 1», оставляя последовательность « +2+3» в соответствие с « b». Но, таким образом:
Match a b, 1 { db a }.
Для « b» ничего не остается, и блок вообще не будет обработан.
Блок кода, определенный директивой « match» обрабатывается так же, как любая макроинструкция, поэтому здесь могут использоваться любые операторы, специфичные для макроинструкций.
Что делает директиву « match» очень полезной, так это тот факт, что она заменяет символьные константы на их значения в поставленной в соответствие последовательности символов (то есть везде после запятой до начала блока кода) перед началом сопоставления. Благодаря этому директива может использоваться, например, для обработки некоторого блока кода в зависимости от выполнения условия, что данная символьная константа имеет нужное значение, например:
Match =TRUE, DEBUG { include 'debug.inc' }.
Здесь файл будет включен, только если символьная константа « DEBUG» определена со значением « TRUE».
2.3.7 Порядок обработки.
При сочетании разных свойств препроцессора важно знать порядок, в котором они обрабатываются. Кат уже было отмечено, высший приоритет имеет директива « fix» и замены, ею определенные. Это полностью делается перед совершением любого другого препроцессинга, поэтому такой кусок кода:
V fix {
Macro empty.
V.
V fix }.
V.
Делает допустимое определение пустого макроса. Можно сказать, что директива « fix» и приоритетные константы обрабатываются на отдельной стадии, и весь остальной препроцессинг делается на результирующем коде.
Стандартный препроцессинг, который начинается после, на каждой строке начинается с распознавания первого символа. Сначала идет проверка на директивы препроцессора, и если ни одна из них не опознана, препроцессор проверяет, является ли первый символ макроинструкцией. Если макроинструкция не найдена, препроцессор переходит ко второму символу на строке, и снова начинает с проверки на директивы, список которых в этом случае ограничивается лишь « equ», так как только она может оказать вторым символом на строке. Если нет директивы, второй символ проверяется на структурную макроинструкцию, и если ни одна из этих проверок не дала положительного результата, символьные константы заменяются на их значения, и строка передается ассемблеру.
Продемонстрируем это на примере. Пусть « foo» — это макрос, а « bar» — это структура. Эти строки:
Foo equ.
Foo bar.
Обе будут интерпретированы как вызовы макроса « foo», так как значение первого символа берет верх над значением второго.
Макроинструкции генерируют новые строки от их блоков определения, заменяя параметры на их значения и далее обрабатывая операторы « #» и « `». Оператор конверсии имеет высший приоритет, чем оператор сцепления.
После завершения этого, заново сгенерированная строка проходит через стандартный препроцессинг, как описано выше.
Хотя обычно символьные константы заменяются исключительно в строках, нет ни директив препроцессора, ни макроинструкций, встречается несколько особых ситуаций, где замены проводятся в частях строк, содержащих директивы. Первая — это определение символьной константы, где замены производятся везде после слова « equ» и результирующее значение присваивается новой константе (смотрите 2.3.2). Вторая такая ситуация — это директива « match», где замены производятся в символах, следующих за запятой перед сопоставлением их с образцом. Эти свойства могут использоваться, например, для сохранения списков, как, например, эта совокупность определений:
List equ.
Macro append item.
{
Match any, list \{ list equ list,item \}.
Match, list \{ list equ item \}.
}
Здесь константа « list» инициализируется с пустым значением, и макрос « append» может использоваться для добавления новых пунктов к списку, разделяя их запятыми. Первое сопоставление в этом макросе происходит, только если значение списка непусто (смотрите 2.3.6), таким образом новое его значение — это предыдущее с запятой и новым пунктом, добавленным в конец. Второе сопоставление происходит, только если список все еще пуст, и таким образом список определяется как содержащий только лишь новый пункт. Так, начиная с пустого списка, « append 1» определит « list equ 1», а « append 2», следующий за ним, определит « list equ 1,2». Может потребоваться использовать этот список как параметры к некоторому макросу. Но это нельзя сделать прямо — если « foo» это макрос, то в строке « foo list» символ « list» просто прошел бы как параметр к макросу, поскольку символьные константы на этой стадии ещё не развернуты. Для этой цели снова оказывается удобна директива « match»:
Match params, list { foo params }.
Значение « list», если оно не пустое, соответствует ключевому слову « params», которое далее во время генерации строк, заключенных в фигурные скобки, заменяется на соответственное значение. Так, если « list» имеет значение « 1,2», строка, указанная выше, сгенерирует строку, содержащую « foo 1,2», которая далее пройдет стандартный препроцессинг.
Есть ещё один особый случай — когда препроцессор собирается проверить второй символ и натыкается на двоеточие (что далее интерпретируется ассемблером как определение метки), он останавливается в этом месте и заканчивает препроцессинг первого символа (то есть если это символьная константа, она развертывается) и если это все еще выглядит меткой, совершается стандартный препроцессинг, начиная с места после метки. Это позволяет поместить директивы препроцессора и макроинструкции после меток, аналогично инструкциям и директивам, обрабатываемым ассемблером, например:
Start: include 'start.inc'.
Однако если метка во время препроцессинга разрушается (например, если у символьной константы пустое значение), происходит только замена символьных констант до конца строки.