У макросов могут быть так называемые групповые аргументы. Это позволяет использовать переменное количество аргументов. При определении макроса, групповой аргумент заключается в квадратные скобочки
[
и ]
:
Синтаксис:
macro name arg1, arg2, [grouparg]
{
; тело макроса
}
Среди аргументов в определении макроса, групповой аргумент должен быть последним. Групповой аргумент может содержать несколько значений:
macro name arg1,arg2,[grouparg] {}
name 1,2,3,4,5,6
В этом примере значение
arg1
будет 1
, arg2
— 2
, а grouparg
— 3,4,5,6
.
Для работы с групповыми аргументами применяются специальные директивы препроцессора. Они могут быть использованы только внутри тела макроса имеющего групповой аргумент. Первая такая директива — это
COMMON
. Она означает, что после неё имя группового аргумента будет замещаться всеми аргументами сразу:
macro string [grp]
{
common
db grp,0
}
string 'aaaaaa'
string 'line1',13,10,'line2'
string 1,2,3,4,5
получим:
db 'aaaaaa',0
db 'line1',13,10,'line2',0
db 1,2,3,4,5,0
Аргументы можно обрабатывать и по-отдельности. Для этого служит директива
FORWARD
. Часть тела макроса после этой директивы обрабатывается препроцессором для каждого аргумента из группы:
macro a arg1,[grparg]
{
forward
db arg1
db grparg
}
a 1,'a','b','c'
a -1, 10, 20
будет:
db 1
db 'a'
db 1
db 'b'
db 1
db 'c'
db -1
db 10
db -1
db 20
Директива
FORWARD
работает по умолчанию для макросов с групповыми аргументами, так что предыдущий пример можно сделать так:
macro a arg1,[grparg]
{
db arg1
db grparg
}
REVERSE
— это аналог FORWARD
, но обрабатывает группу аргументов в обратном порядке — от последнего к первому:
macro a arg1,[grparg]
{
reverse
db arg1
db grparg
}
a 1,'a','b','c'
получим:
db 1
db 'c'
db 1
db 'b'
db 1
db 'a'
3 вышеупомянутые директивы могут разделять тело макроса на блоки. Каждый блок обработается препроцессором после предыдущего. Например:
macro a [grparg]
{
forward
f_#grparg: ;оператор объединения
common
db grparg
reverse
r_#grparg:
}
a 1,2,3,4
будет:
f_1:
f_2:
f_3:
f_4:
db 1,2,3,4
r_4:
r_3:
r_2:
r_1:
У локальных меток в макросах есть ещё одно полезное свойство. Если директива
LOCAL
находится внутри блока FORWARD
или REVERSE
, то уникальное имя метки сгенерируется для каждого аргумента из группы, и в последующих блоках FORWARD
и/или REVERSE
для каждого аргумента будет использована соответствующая ему метка:
macro string_table [string]
{
forward ;таблица указателей на строки
local addr ;локальная метка для строки
dd addr ;указатель на строку
forward ;строки
addr db string,0 ;создаём и завершаем нулём
}
string_table 'aaaaa','bbbbbb','5'
получим:
dd addr?00000001
dd addr?00000002
dd addr?00000003
addr?00000001 db 'aaaaa',0
addr?00000002 db 'bbbbbb',0
addr?00000003 db '5',0
Другой пример с блоком
REVERSE
:
macro a [x]
{
forward
local here
here db x
reverse
dd here
}
a 1,2,3
будет:
here?00000001 db 1
here?00000002 db 2
here?00000003 db 3
dd here?00000003
dd here?00000002
dd here?00000001
Как видно, метки используется с соответствующими аргументами и в
FORWARD
и в REVERSE
блоках.
Возможно использовать и несколько групповых аргументов. В этом случае определение макроса не будет выглядеть как:
macro a [grp1],[grp2]
так как тут не ясно какой аргумент какой группе принадлежит. Исходя из этого делают так:
Синтаксис:
macro a [grp1,grp2]
В этом случае каждый нечётный аргумент относится к группе
grp1
, а каждый чётный — к grp2
:
macro a [grp1,grp2]
{
forward
l_#grp1:
forward
l_#grp2:
}
a 1,2,3,4,5,6
будет:
l_1:
l_3:
l_5:
l_2:
l_4:
l_6:
Или ещё:
macro ErrorList [name,value]
{
forward
ERROR_#name = value
}
ErrorList \
NONE,0,\
OUTOFMEMORY,10,\
INTERNAL,20
получим:
ERROR_NONE = 0
ERROR_OUTOFMEMORY = 10
ERROR_INTERNAL = 20
Конечно же, может быть больше 2х групп аргументов:
macro a [g1,g2,g3]
{
common
db g1
db g2
db g3
}
a 1,2,3,4,5,6,7,8,9,10,11
будет:
db 1,4,7,10
db 2,5,8,11
db 3,6,9