Monday, March 18, 2019

Руководство пользователя flat assembler g

ENtoRU перевод flat assembler g user manual (update 16 Jun 2019)
последняя правка 17.06.2019

Подробное описание синтаксиса flat assembler g. Это справочный документ, который также может быть прочитан в качестве последовательного руководства.

0. Исполнение ассемблера
1. Основные синтаксические правила
2. Символовы-идентификаторы
3. Основные определения символов
4. Значения выражений
5. Классы символов
6. Генерирование данных
7. Условная сборка
8. Макроинструкции
9. Помеченные макроинструкции
10. Символьные переменные
11. Повторяющиеся блоки инструкций
12. Сопоставление параметров
13. Области выдачи
14. Прочие инструкции

В этом документе описывается синтаксис языка ассемблера flat assembler g с основными примерами. Он был написан в предположении, что он будет прочитан последовательно, и в любом его месте используются только те понятия и конструкции, которые были введены ранее. Однако, возможно переходить прямо к разделу, который интересует читателя, а затем возвращаться к более ранним частям, когда это необходимо, чтобы лучше понять более поздние.

0. Исполнение ассемблера


Для запуска сборки из командной строки необходимо указать хотя бы один параметр: имя исходного файла и, необязательно, второй - имя файла назначения. Если сборка прошла успешно, сгенерированный результат записывается в место назначения и отображается краткая сводка, в противном случае выводится информация об ошибках. Максимальное количество отображаемых ошибок можно контролировать с помощью дополнительного ключа "-e" (по умолчанию отображается не более одной ошибки). Ключ "-p" определяет максимальное количество проходов, которые ассемблеру надлежит сделать. По умолчанию этот предел равен 100. Ключ "-r" позволяет установить ограничение стека рекурсии, то есть максимально допустимую глубину вложения макроинструкций и включений дополнительных исходных файлов. Переключатель "-v" включает отображение всех строк из этого стека при сообщении об ошибке (по умолчанию ассемблер пытается выбрать только те строки, которые, вероятно, наиболее информативны, но эта простая эвристика не всегда корректна). Если ключ "-v" используется со значением 2, он дополнительно делает все сообщения, отображаемые командами из исходного текста, показываемыми в реальном времени (при каждом последующем проходе). Переключатель "-i" позволяет вставить любую команду в начало обрабатываемого исходника.

1. Основные синтаксические правила


Каждая команда на языке ассемблера занимает одну строку текста. Если строка содержит точку с запятой, всё от этого знака до конца строки рассматривается как комментарий и игнорируется ассемблером. Основная (незакомментарованная) часть строки может заканчиваться знаком обратной косой черты, и в этом случае следующая строка из исходного текста будет присоединена ​​к предыдущей. Это позволяет разбить любую команду на несколько строк, когда это необходимо. Отныне исходную строку будем понимать, как результат удаления комментариев и соединения строк текста, связанных символами обратной косой черты.
Текст исходной строки делится на синтаксические единицы, называемые токенами. Существует ряд специальных знаков, которые сами становятся отдельными токенами. Любой из знаков, перечисленных ниже, является такой синтаксической единицей:
+-/*=<>()[]{}:?!.,|&~#`\
Любая единая (то есть не разбитая пробелами) последовательность знаков, отличных от указанных выше, становится единым токеном, который может быть именем или числом. Исключением из этого правила является случай, когда последовательность начинается со знака одинарной или двойной кавычки. Так определяется строка в кавычках, и она может содержать любые специальные знаки, пробелы и даже точки с запятой, поскольку она заканчивается, только когда встречается тот же знак, который использовался для её начала. Кавычки, которые используются для заключения строки, сами не становятся частью строки. Если необходимо определить строку, содержащую тот же знак, который используется для её определения, этот знак должен быть удвоен внутри строки — только один экземпляр знака станет частью строки, и последовательность будет продолжена.
Числа отличаются от имён тем, что они начинаются либо с десятичной цифры, либо со знака "$", за которым следует любая шестнадцатеричная цифра. Это означает, что токен можно считать числовым, даже если он не является допустимым числом. Правильный числовой токен должен быть одним из следующих: десятичное число (с необязательной буквой "d" на конце); двоичное число, оканчивающееся буквой "b"; восьмеричное число, оканчивающееся буквой "o" или "q"; или шестнадцатеричное число, начинающееся с "$" или "0x", или оканчивающееся буквой "h". Поскольку первая цифра шестнадцатеричного числа может быть буквой, может потребоваться добавить в нее цифру ноль, чтобы её можно было идентифицировать как число. Например, "0Ah" - это допустимое число, а "Ah" - просто имя.

2. Символовы-идентификаторы


Любое имя может стать определяемым символом, если оно получает какое-то значение. Один из самых простых способов создания символа с заданным значением — использоватть команду "=":
а = 1
Команда ":" определяет метку, которая является символом со значением, равным текущему адресу в сгенерированном выходе. В начале исходного текста этот адрес всегда равен нулю, поэтому, если исходный файл начинается со следующих двух команд, они определяют символы с одинаковыми значениями:
first:
second = 0
Метка, определённая с помощью команды ":", является специальной конструкцией на ассемблере — за ней (в той же строке) может следовать любая другая команда (включая определение другой метки). Это единственная команда, которая позволяет делать такое.
В таком определении перед знаком ":" или "=" стоит символ-идентификатор. Им может быть простое имя, как в приведённых выше примерах, но он также может содержать некоторые дополнительные модификаторы, описанные ниже.
Когда имя в определении символа имеет знак "?", добавленный к нему (без пробелов между ними), символ не чувствителен к регистру (в противном случае он будет определён как регистрозависимый). Это означает, что к значению такого символа можно обратиться (как в выражении справа от знака "=") по имени, являющимся любым вариантом исходного имени, который отличается только регистром букв. Впрочем, разрешено различать только регистры 26 букв английского алфавита.
Можно определить регистрозависимый символ, который конфликтует с регистронезависимым. Тогда регистрозависимый символ имеет приоритет, а более общий символ используется только тогда, когда соответствующий регистрозависимый символ не определён. Это можно исправить с помощью модификатора "?", так как он всегда означает, что имя, за которым он следует, относится к регистронезависимому символу.
tester? = 0
tester = 1
TESTER = 2
x = tester       ; x = 1
y = Tester       ; y = 0
z = TESTER       ; z = 2
t = tester?      ; t = 0
Каждый символ имеет своё собственное пространство имён потомков, называемое дочерним пространством имён. Когда два имени связаны точкой (без пробелов между ними), такой идентификатор относится к символу, указанному вторым, который находится в пространстве имён потомков символа, указанного первым. Эта операция может повторяться много раз в пределах одного идентификатора, что позволяет ссылаться на потомков потомков в цепочке любой длины.
space:
space.x = 1
space.y = 2
space.color:
space.color.r = 0
space.color.g = 0
space.color.b = 0
Любое из имён в такой цепочке может произвольно сопровождаться знаком "?", означающем, что оно относится к регистронезависимому символу. Если "?" вставляется в середину имени (таким образом разделяя его на отдельные токены), то такой идентификатор считается синтаксической ошибкой.
Когда идентификатор начинается с точки (другими словами: когда имя родительского символа пусто), он ссылается на символ в пространстве имён самой последней обычной метки, определённой перед ним. Это позволяет переписать приведённый выше пример следующим образом:
space:
.x = 1
.y = 2
.color:
.color.r = 0
.color.g = 0
.color.b = 0
После того, как метка "space" определена, она становится самой последней определённой обычной меткой, поэтому следующий ".x" относится к символу "space.x", а затем ".color" относится к "space.color".
Команда "namespace", за которой следует символьный идентификатор, изменяет базовое пространство имён для раздела исходного текста. Ей должна соответствовать команда "end namespace" далее в исходном тексте, отмечающая конец такого блока. Это можно использовать, чтобы снова переписать приведённый выше пример другим способом:
space:
namespace space
  x = 1
  y = 2
  color:
  .r = 0
  .g = 0
  .b = 0
end namespace
Когда имени не предшествует точка, и как таковое оно не имеет явного указания, в каком пространстве имён находится символ, ассемблер ищет определяемый символ в текущем пространстве имён и, если ни один не найден, в последовательных пространствах имён родительских символов начиная с пространства имён, содержащего родительский символ текущего пространства имён. Если определяемый символ с таким именем не найден, предполагается, что имя относится к символу в текущем пространстве имён (и, если после такого имени нет знака "?", предполагается, что символ чувствителен к регистру). Определение, которое не определяет пространство имён, в котором должен быть создан новый символ, всегда создает новый символ в текущем базовом пространстве имён.
global = 0
regional = 1
namespace regional
  regional = 2            ; regional.regional = 2
  x = global              ; regional.x = 0
  regional.x = regional   ; regional.regional.x = 2
  global.x = global       ; global.x = 0
end namespace
Комментарии в приведённом выше примере разъясняют эквивалентные определения относительно исходного базового пространства имён. Обратите внимание, что когда имя используется для указания пространства имён, ассемблер ищет уже определённый символ с таким именем для просмотра в его пространстве имён, но когда это имя определяемого символа, он всегда создаётся в текущем базовом пространстве имён.
Если за последней точкой идентификатора не следует никакого имени, он ссылается на родительский символ пространства имён, в котором выполнялся бы поиск символа, как если бы после этой точки было имя. Добавление такой точки в конце идентификатора может показаться излишним, но его можно использовать, чтобы изменить способ определения символа, поскольку он побуждает ассемблер искать уже существующий символ, который он может изменить, вместо того, чтобы просто создавать новый в текущем пространстве имён. Например, если в четвёртой строке предыдущего примера "regional" заменить на "regional.", то будет перезапиcано значение исходного символа "regional" вместо создания нового символа в дочернем пространстве имён. Точно так же, определение, сформированное таким образом, может назначить символу новое значение независимо от того, был ли он ранее определён как чувствительный к регистру или нет.
Если идентификатор — это лишь одна точка, то по вышеприведённым правилам он относится к самой последней метке, которая не начиналась с точки. Так приведённый выше пример можно переписать ещё одним способом:
space:
  namespace .
    x = 1
    y = 2
    color:
    namespace .
      r = 0
      g = 0
      b = 0
  end namespace
end namespace
Он также показывает, как разделы пространства имён могут быть вложены друг в друга.
Знак "#" может быть вставлен в любое место внутри идентификатора без изменения его смысла. Когда "#" является единственным символом, разделяющим два именных токена, это приводит к тому, что они интерпретируются как одно имя, образованное путем соединения токенов.
variable = 1
varia#ble = var#iable + 2       ; variable = 3
Это, также, применимо к числам.
Внутри блока, определённого с помощью "namespace", изначально нет метки, которая считалась бы базовой для идентификаторов, начинающихся с точки (однако метка, которая служила этой цели ранее, возвращается к использованию после "end namespace"). Это же происходит в начале исходного текста, до определения какой-либо метки. Это связано с парой дополнительных правил, касающихся использования точек в идентификаторах.
Если идентификатор начинается с точки, но нет метки, которая была бы для него родительской, этот идентификатор ссылается на "потомка" специального символа, который находится в текущем пространстве имён, но не имеет имени. Если идентификатор начинается с последовательности из двух или более точек, идентификатор ссылается на "потомка" аналогичного безымянного символа, и такие безымянные символы различны для разного количества точек. В то время как пространство имён, доступ к которому осуществляется с помощью одной начальной точки, изменяется каждый раз, когда определяется новая обычная метка, специальное пространство имён, доступ к которому осуществляется с помощью двух или более точек в начале идентификатора, остаётся неизменным:
first:
  .child = 1
  ..other = 0
second:
  .child = 2
  ..another = ..other
В этом примере смысл идентификатора ".child" зависит от места, а у идентификатора "..other" — везде один и тот же.
Когда два имени внутри идентификатора разделены последовательностью из двух или более точек, идентификатор относится к потомку такого специального безымянного символа в пространстве имён, указанном идентификатором стоящим перед этой последовательностью точек. Безымянное дочернее пространство имён выбирается в зависимости от количества точек, и в этом случае количество требуемых точек увеличивается на единицу. В следующем примере демонстрируются два метода идентификации такого символа:
namespace base
  ..other = 1
end namespace

result = base.#..other
Знак "#" был вставлен в последний идентификатор для лучшей удобочитаемости, но простая последовательность из трёх точек даёт то же самое.
К безымянному символу, владеющему специальным пространством имён, можно получить доступ, через идентификатор заканчивающийся последовательностью из двух или более точек — благодаря правилу о том, что идентификатор, заканчивающийся точкой, относится к родительскому символу пространства имён, к которому относилось бы имя после этой точки. Таким образом, в контексте предыдущего примера "base..." (или "base.#..") будет ссылаться на безымянного родителя пространства имён, в котором находится символ "other", и это будет тот же символ, который идентифицируется просто с помощью ".." внутри пространства имён символа "base".
Любому идентификатору может предшествовать знак "?" и этот модификатор даёт дополнительный эффект, только когда он используется в контексте, где идентификатор может означать нечто иное, чем метка или переменная, которая должна быть определена. Этот модификатор затем подавляет любую другую интерпретацию. Например, идентификатор, начинающийся с "?" не будет рассматриваться как инструкция, даже если это первый символ в строке. Это можно использовать для определения переменной, которая имеет общее имя с существующей командой:
?namespace = 0
Число может использоваться в роли имени внутри идентификатора, но не тогда, когда оно стоит в начале, потому что тогда оно считается литеральным значением. Это ограничение также можно обойти, предварив идентификатор знаком "?".

3. Основные определения символов


Если символ определяется как метка, это орпеделение должно быть единственным во всём исходнике. Значение, назначенное символу таким образом, может быть получено из любого места в исходнике, даже до того, как метка будет фактически определена. Если символ используется до того, как он определён (это часто называется ссылкой вперёд), ассемблер пытается правильно предсказать значение символа, делая несколько проходов по исходному тексту. Только если все предсказания окажутся правильными, ассемблер генерирует окончательный выход.
Символ, который может быть определён только один раз и, таким образом, имеет универсальное значение, на которое всегда может быть ссылка вперёд, называется константой. Все метки являются константами.
Если символ определяется командой "=", он может иметь несколько определений такого вида. Такой символ называется переменной, и когда он используется, доступным будет значение из его последнего определения. Символ, определённый с помощью такой команды, также может иметь ссылку вперёд, но только в том случае, если он определён ровно один раз во всём исходнике и как таковой имеет одно значение.
a = 1           ; a = 1
a = a + 1       ; a = 2
a = b + 1       ; a = 3
b = 2
Частным случаем ссылки вперёд является самоссылка, когда значение символа используется в его собственном определении. Сборка такой конструкции проходит успешно, только когда ассемблер может найти значение, неизменное при таком означивании, эффективно решая уравнение. Однако из-за простоты алгоритма разрешения, основанного на предсказаниях, решение не всегда может быть найдено, даже если оно существует.
x = (x-1)*(x+2)/2-2*(x+1)       ; x = 6 или x = -1
Команда ":=" определяет константу. Она может использоваться вместо "=", чтобы гарантировать, что данный символ определён ровно один раз и что на него можно сделать ссылку вперёд.
Команда "=:" определяет переменную подобно "=", но отличается тем, как обрабатывается предыдущее значение (если оно существует). Хотя "=" отбрасывает предыдущее значение, "=:" сохраняет его, чтобы позднее его можно было вернуть с помощью команды "restore":
a = 1
a =: 2          ; сохраняет a = 1
a = 3           ; сбрасывает a = 2 и заменяет его на a = 3
restore a       ; восстанавливает a = 1
За командой "restore" может следовать несколько символов-идентификаторов, разделённых запятыми;  она отбрасывает последнее определение каждого из них. Не считается ошибкой использовать "restore" с символом, который не имеет действующего определения (либо потому, что он никогда не был определён, либо потому, что все его определения уже были отброшены ранее). Если символ обрабатывается командой "restore", он не может иметь ссылку вперёд — по этой причине "restore" не может быть применено к константам.
Ключевое слово "label", за которым следует символ-идентификатор, является альтернативным способом определения метки. В этой основной форме он эквивалентен определению с помощью ":", но занимает целую строку. Однако с помощью этой команды можно предоставить больше настроек для определяемой метки. За идентификатором опционально может следовать токен ":", а затем — дополнительное значение, которое будет связано с этой меткой (обычно указывающее размер помечаемого объекта). Ассемблер имеет ряд встроенных констант, определяющих различные размеры для этой цели, но это значение может быть представлено обычным числом.
label character:byte
label char:1
Вместо знака ":" можно обходиться простым пробелом, однако для ясности рекомендуется его оставлять. После идентификатора и необязательного размера может следовать ключевое слово "at", а затем значение, которое должно быть назначено метке вместо текущего адреса.
label wchar:word at char
Встроенные константы размера эквивалентны следующему набору определений:
byte? = 1       ; 8 бит
word? = 2       ; 16 бит
dword? = 4      ; 32 бита
fword? = 6      ; 48 бит
pword? = 6      ; 48 бит
qword? = 8      ; 64 бит
tbyte? = 10     ; 80 бит
tword? = 10     ; 80 бит
dqword? = 16    ; 128 бит
xword? = 16     ; 128 бит
qqword? = 32    ; 256 бит
yword? = 32     ; 256 бит
dqqword? = 64   ; 512 бит
zword? = 64     ; 512 бит
Ключевое слово "element", за которым следует символ-идентификатор, определяет специальную константу, которая не имеет фиксированного значения и может использоваться в качестве переменной в линейных многочленах. За идентификатором опционально может следовать токен ":", а затем значение, которое должно быть связано с этим символом, называемое метаданными элемента.
element A
element B:1
Метаданные, назначенные символу, могут быть извлечены с помощью специального оператора, определяемого в следующем разделе.

4. Значения выражений


В каждой описанной выше конструкции, где было предоставлено какое-либо значение, например после команды "="" или после ключевого слова "at", это значение может быть литералом (числом или строкой в кавычках) или символом-идентификатором. Значение также может быть указано с помощью выражения, содержащего встроенные операторы.
"+", "-" и "*" выполняют стандартные арифметические операции над целыми числами ("+" и "-" также могут использоваться в унарной форме — с одним аргументом). "/" и "mod" выполняют деление с остатком, давая неполное частное и остаток, соответственно. Среди этих арифметических операторов "mod" имеет наивысший приоритет (он вычисляется первым), "*" и "/" следуют за ними, а "+" и "-" вычисляются последними (даже в их унарных вариантах). Операторы с одинаковым приоритетом выполняются слева направо. Круглые скобки используются для заключения в них подвыражений, когда требуется изменить порядок операций.
"xor", "and" и "or" выполняют побитовые операции над числами. "xor" — это битовое сложение (исключающее "или"), "and" — побитовое умножение, а "or" — включающее "или" (дизъюнкция). Эти операторы имеют более высокий приоритет, чем любые арифметические операторы.
"shl" и "shr" выполняют сдвиг битов первого аргумента на количество битов, указанное вторым. "shl" сдвигает биты влево (к более высоким степеням двойки), в то время как "shr" сдвигает биты вправо (к нулю), отбрасывая биты, попадающие в дробные разряды. Эти операторы имеют более высокий приоритет, чем другие двоичные побитовые операции.
"not", "bsf" и "bsr" являются унарными операторами с ещё более высоким приоритетом. "not" инвертирует все биты числа, в то время как "bsf" и "bsr" отыскивают младший или старший установленный бит соответственно и в результате дают индекс этого бита (номер разряда).
Все операции над числами выполняются так, как если бы они выполнялись над бесконечными двочными представлениями этих чисел. Например, "bsr" с отрицательным числом в качестве аргумента не даёт действенного результата, поскольку двоичное представление такого числа имело бы бесконечную последовательность установленных битов, и, как таковое, не содержит старшего установленного бита (это сигнализируется как ошибка).
Оператор "bswap" позволяет создать строку байтов, содержащую представление числа в обратном порядке байтов (от старшего к младшему, big endian). Второй аргумент этого оператора должен быть длиной (в байтах) требуемой строки. Этот оператор имеет тот же приоритет, что и операторы "shl" и "shr".
Когда строковое значение используется в качестве аргумента любой из операций над числами, оно обрабатывается как последовательность битов и автоматически преобразуется в положительное число (расширяемое нулевыми битами до бесконечности). Символы строки соответствуют битам числа — последовательно от младших к старшим.
Чтобы преобразовать число обратно в строку, можно использовать унарный оператор "string". Этот оператор имеет наименьший возможный приоритет, поэтому, когда он предшествует выражению, оно полностью вычисляется до этого преобразования. Когда необходимо преобразование в обратном направлении, простого унарного "+" достаточно, чтобы строка стала числом.
Длина строки может быть получена с помощью унарного оператора "lengthof". Этот оператор может быть применён только к строке. Это один из операторов с наивысшим приоритетом.
Если в выражении используется символ, определённый с помощью команды "element",
результатом может быть линейный многочлен от переменной, представленной этим символом. Над членами многочлена допустимо выполнять только простые арифметические операции, так чтобы результат так же был линейным многочленом. Например, разрешено умножать многочлен на число, но не на другой многочлен.
Существует несколько операторов с высоким приоритетом, которые позволяют извлекать информацию о членах линейного многочлена. Здесь многочлен должен быть первым аргументом, а индекс (номер) его члена — вторым. Оператор "element" извлекает переменную члена многочлена (с коэффициентом один), оператор "scale" извлекает коэффициент (число, на которое умножается переменная), а оператор "metadata" возвращает метаданные, связанные с переменной.
Если вторым аргументом стоит число превышающее индекс последнего члена многочлена, то все три оператора возвращают ноль. Когда второй аргумент равен нулю, "element" и "scale" выдают соответствующую информацию о свободном члене многочлена: "element" возвращает числовое значение 1, а "scale" — сам свободный член многочлена.
element A
linpoly = A + A + 3
vterm = linpoly scale 1 * linpoly element 1     ; vterm = 2 * A
cterm = linpoly scale 0 * linpoly element 0     ; cterm = 3 * 1
Оператор "metadata" с нулевым индексом возвращает размер первого аргумента. Это значение является определённым только в том случае, если первый аргумент является символом, с которым связан размер (или арифметическое выражение, содержащее такой символ), в противном случае он равен нулю. Существует дополнительный унарный оператор "sizeof", который даёт то же значение, что и "metadata 0".
label table : 256
length = sizeof table   ; length = 256
"elementof", "scaleof" и "metadataof" являются вариантами операторов "element", "scale" и "metadata" с обратным порядком аргументов. Поэтому, когда "sizeof" используется в выражении, это эквивалентно записи "0 metadataof". Эти операторы имеют даже более высокий приоритет, чем их аналоги, и являются правоассоциативными.
Порядок членов линейного многочлена зависит от того, как было построено значение. Каждая арифметическая операция сохраняет порядок членов в первом аргументе, а члены, которых не было в первом аргументе, присоединяются в конец в том же порядке, в котором они встречались во втором аргументе. Этот порядок имеет значение только при извлечении членов соответствующими операторами.
"elementsof" — это ещё один унарный оператор наивысшего приоритета, он подсчитывает количество переменных членов линейного многочлена.
Выражение также может содержать литеральное значение, которое определяет число с плавающей точкой. Такое число должно быть в десятичной записи, оно может содержать символ "." в качестве разделителя целых разрядов от дробных. За этим числом может следовать символ "е", а затем десятичное значение показателя степени, предваряемое знаком "+" (необязательно) или "-" для обозначения знака этого показателя степени. Если в таком литерале имеется "." или "е" , то за ними должна следовать хотя бы одна цифра. Символ "f" может быть добавлен в конец такого литерального значения. Если числовой литерал не содержит ни "." ни "е", постфикс "f" — единственный способ убедиться, что он рассматривается как число с плавающей точкой, а не как обычное десятичное целое число.
Числа с плавающей точкой обрабатываются ассемблером в двоичной форме. Их диапазон и точность, по крайней мере, столь же велики, как и в самом длинном формате с плавающей точкой, который ассемблер может сгенерировать на выходе.
Основные арифметические операции могут иметь число с плавающей точкой в качестве любого из аргументов, но тогда ни один из аргументов не может содержать нескалярных членов (линейных многочленов). Результатом такой операции всегда является число с плавающей точкой.
Унарный оператор "float" может использоваться для преобразования целочисленного значения в число с плавающей точкой. Этот оператор имеет наивысший приоритет.
"trunc" — это еще один унарный оператор с наивысшим приоритетом, и его можно применять к числам с плавающей точкой. Он извлекает целую часть числа (усечение в сторону уменьшения), и результатом всегда является обычное целое число, а не число с плавающей точкой. Если аргумент уже был целым числом, эта операция оставляет его без изменений.
Оператор "bsr" может быть применён к числам с плавающей точкой, и он возвращает показатель наибольшей степени двойки, не превосходящей данного числа. Знак числа с плавающей точкой не влияет на результат этой операции.
Также разрешено использовать числа с плавающей точкой в качестве первого аргумента для операторов "shl" и "shr". Затем число умножается или делится на степень двойки, указанную вторым аргументом.

5. Классы символов


Существуют три различных класса символов, определяющих позицию в исходной строке, в которой символ может быть распознан. Символ, принадлежащий классу инструкций, распознаётся только в том случае, если он является первым идентификатором команды, тогда как символ из класса выражений распознается, только когда он используется для предоставления значения аргументов какой-либо команде.
Определения всех типов, описаных в предыдущих разделах, создают символы класса выражений. "label" и "restore" являются примерами встроенных символов, принадлежащих к классу инструкций.
В любом пространстве имён разрешено, чтобы символы разных классов имели одно и то же имя, например, можно определить инструкцию с именем "shl", в то время как существует также оператор с тем же именем, принадлежащий классу выражений (как и все операторы).
Третий класс символов — это помеченные инструкции. Символ, принадлежащий этому классу, может быть распознан, только если первый идентификатор команды не является инструкцией — в таком случае первый идентификатор становится меткой для инструкции, определенной вторым идентификатором. Примером помеченной инструкции может служить "=", если его рассматривать как особый вид идентификатора.
Правила, касающиеся пространств имён, в равной степени применимы к символам всех классов, например, символ из класса инструкций, принадлежащий дочернему пространству имён последней метки, может быть выполнен, если перед его именем ставится точка. Однако следует отметить, что когда пространство имён указывается через его родительский символ,  исходный символ всегда является символом, принадлежащим классу выражений. Невозможно обратиться к дочернему пространству имён инструкции, только к пространству имён, принадлежащему символу из класса выражения (с таким же именем).
Ассемблер содержит встроенные символы всех классов. Их имена всегда нечувствительны к регистру, и они могут быть переопределены, но удалить их невозможно. Когда все значения такого символа удаляются командой, такой как "restore", остаётся встроенное значение.

6. Генерирование данных


Инструкция "db" позволяет генерировать байты данных и помещать их в выход ассемблера. За ней должно следовать одно или несколько значений, разделённых запятыми. Когда значение является числовым, оно определяет один байт. Когда значение является строкой, оно помещает строку байтов в выход.
db 'Hello',13,10        ; генерирует 7 байт
Ключевое слово "dup" может использоваться для генерирования одного и того же значения несколько раз подряд. Команде "dup" предшествует числовое выражение, определяющее количество повторений, а за ней следует значение, которое должно повторяться. Последовательность значений может дублироваться таким же образом. В таком случае за "dup" должна следовать заключенная в скобки последовательность значений, разделенных запятыми.
db 4 dup 90h            ; генерирует 4 байта
db 2 dup ('abc',10)     ; генерирует 8 байт
Если в качестве значения в аргументах команды "db" используется специальный идентификатор, состоящий из одного знака "?", она резервирует один байт. При этом текущий адрес в выходе увеличивается так, чтобы затем был помещён следующий блок данных. Однако зарезервированные байты не генерируются, если за ними не следуют некоторые другие данные. Поэтому, если байты зарезервированы в конце выхода, они не увеличивают размер сгенерированного файла. Данные такого вида называются неинициализированными, тогда как все обычные данные называются инициализированными.
Инструкция "rb" резервирует количество байтов, указанное в аргументе.
db ?                    ; резервирует 1 байт
rb 7                    ; резервирует 7 байт
Каждая встроенная инструкция, которая генерирует данные (традиционно называемая директивой данных), связана с помеченной инструкцией с тем же именем. Такая команда кроме генерирования данных определяет метку по адресу генерируемых данных, со связанным с ней размером, равным размеру единицы данных, используемой этой инструкцией. В случае "db" и "rb" этот размер равен 1.
some db sizeof some     ; генерирует байт со значением 1
"dw", "dd", "dp", "dq", "dt", "ddq", "dqq" и "ddqq" являются инструкциями, аналогичными "db" с различными размерами единицы данных. Порядок байтов в пределах одной сгенерированной единицы всегда — от младшего к старшему (little-endian). Когда последовательность байтов передаётся в качестве значения любой из этих инструкций, сгенерированная последовательность байтов расширяется нулевыми байтами до длины, кратной единице данных. "rw", "rd", "rp", "rq", "rt", "rdq", "rqq" и "rdqq" — это инструкции, которые резервируют указанное количество единиц данных. Размеры единиц данных, которые относятся к этим инструкциям, перечислены в таблице 1.
Инструкции "dw", "dd", "dq", "dt" и "ddq" моуг принимать числа с плавающей точкой в ​​качестве блоков данных. Любое такое число затем преобразуется в формат с плавающей точкой, соответствующий данному размеру.
"emit" (с синонимом "dbx") — это директива данных, которая использует размер единицы данных, указанный в первом аргументе, для генерации данных, определяемых остальными аргументами. Размер может быть отделён от следующего аргумента двоеточием вместо запятой для лучшей читаемости. Если размер единицы данных  имеет при себе директиву данных, определение, сделанное с помощью "emit", имеет тот же эффект, как если бы эти значения были переданы инструкции, специфичной для этого размера.
emit 2: 0,1000,2000      ; генерирует три 16-битных значения
Инструкция "file" читает данные из внешнего файла и записывает их в выход. Аргументом должна быть строка, содержащая путь к файлу, за которой опционально может следовать ":" и числовое значение, указывающее смещение в файле, после него может следовать запятая и числовое значение, указывающее, количество байт для копирования.
file 'data.bin'                 ; вставляет файл целиком
excerpt file 'data.bin':10h,4   ; вставляет выбранные четыре байта
Таблица 1. Директивы данных
Размер (байт) Генерируемые данные Резервируемые данные
1
db
file
rb
2 dw rw
4 dd rd
6 dp rp
8 dq rq
10 dt rt
16 ddq rdq
32 dqq rqq
64 ddqq rdqq
* emit

7. Условная сборка


Инструкция "if" выполняет сборку блока исходного текста только при выполнении условия, определёном логическим выражением, которое является аргументом этой инструкции. Команда "else if" в следующих строках замыкает предшествующий условно собрираемый блок и открывает новый, собираемый только в том случае, если предыдущее условие не было выполнено и новое условие (в аргументе "else if") имеет истинное значение. Команда "else" замыкает предшествующий условно собрираемый блок и начинает блок, который собирается только тогда, когда ни одно из предыдущих условий не было выполнено. Команда "end if" должна использоваться для завершения всей конструкции. Могут быть несколько команд "else if" подряд или ни одной, и не более одного "else".
Логическое выражение синтаксически отличается от основных выражений, описанных ранее. Логическое выражение состоит из логических значений, связанных логическими операторами. Логические операторы могут быть следующими: унарный "~" для отрицания, "&" для конъюнкции и "|" для дизъюнкции. Отрицание вычисляется первым, а "&" и "|" просто вичисляются слева направо, без приоритета друг над другом.
Простейшей формой логического значение может быть основное выражение, тогда оно соответствует истинному условию тогда и только тогда, когда его значение не является нулевой константой. Другой способ создания логического значения — сравнение значения двух основных выражений с помощью одного из следующих операторов: "=" (равно), "<" (меньше), ">" (больше), "<=" (меньше или равно), ">=" (больше или равно), "<>" (не равно).
count = 2
if count > 1
  db '0'
  db count-1 dup ',0'
else if count = 1
  db '0'
end if
Если таким образом сравниваются линейные многочлены, логическое значение корректно, только когда они сравнимы, то есть, если они отличаются только постоянным членом. В противном случае условие типа равенства не является ни всегда истинным, ни всегда ложным, поскольку оно зависит от значений, заменяемых переменными, и ассемблер сообщает об этом как об ошибке.
Оператор "relativeto" создаёт логическое значение, которое истинно только в том случае, если разность сравниваемых значений не содержит переменных членов. Поэтому его можно использовать для проверки сравнимости двух линейных многочленов — условие "relativeto" выполняется только тогда, когда оба сравниваемых многочлена имеют одинаковые переменные члены.
Поскольку логические выражения вычисляются "лениво", можно создать одно условие, которое не вызовет ошибки, если многочлены несравнимы, но сравнит их, если они сравнимы:
if a relativeto b & a > b
  db a - b
end if
Оператор "eqtype" также может использоваться для сравнения двух основных выражений, он создаёт логическое значение, которое истинно, когда значения выражений имеют одинаковый тип — либо оба являются алгебраическими, строками или числами с плавающей точкой. Алгебраический тип включает линейные многочлены и целочисленные значения.
Оператор "eq" сравнивает два основных выражения и создаёт логическое значение, которое истинно, только когда значения выражений имеют одинаковый тип и равны. Этот оператор может использоваться для проверки, является ли значение определённой строкой, определённым числом с плавающей точкой или определённым линейным многочленом. Он может сравнивать значения, которые не сравнимы оператором "=".
Оператор "defined" создаёт логическое значение применяясь к базовому выражению, следующему за ним. Условие выполнено, если выражение не содержит символов, которые не имеют доступного определения. Выражение проверяется только на пригодность его компонентов, ему не нужно иметь вычисляемое значение. Это может использоваться для проверки того, был ли определён символ класса выражений, но, поскольку символ может быть доступен через ссылку вперёд, это условие может быть истинным, даже если символ определён позже в исходнике. Если это нежелательно, то вместо "defined" следует использовать оператор "definite", поскольку он проверяет, все ли символы в следующем далее базовом выражении были определены ранее.
Базовое выражение, которое следует за "defined", также может быть пустым; такое тривиальное условие всегда выполнено. Одноко это не относится к "definite".
Оператор "used" формирует логическое значение, если за ним следует единственный идентификатор. Условие истинно, если значение указанного символа использовалось где-либо в исходнике.
"assert" — это инструкция, которая сигнализирует об ошибке, когда условие, указанное её аргументом, не выполняется.
assert a < 65536

8. Макроинструкции


Команда "macro" позволяет определить новую инструкцию в виде макроинструкции. Блок исходного текста между командой "macro" и "end macro" становится текстом макроинструкции, и эта последовательность строк собирается вместо исходной команды, которая начинается с идентификатора инструкции, определенной таким образом.
macro null
  db 0
end macro

null            ; здесь будет собрано "db 0"
Макроинструкция может иметь аргументы только тогда, когда определение содержит параметры. После "macro" и идентификатора определяемого символа опционально может идти список простых имён, разделённых запятыми. Эти имена определяют параметры макроинструкции. Когда эта инструкция затем используется, за ней могут следовать аргументы, количество которых не превышает количество параметров в определении макроинструкции. Аргументы разделяются запятыми, и их значения последовательно присваиваются параметрам. Перед интерпретацией любой исходной строки внутри макроинструкции токены имён, соответствующие любому из параметров, заменяются присвоенными им значениями.
macro lower name,value
  name = value and 0FFh
end macro

lower a,123h    ; a = 23h
Значением аргумента может быть любой текст, не обязательно правильное выражение. Если строка, вызывающая макроинструкцию, содержит меньше аргументов, чем количество определённых параметров, лишние параметры получают пустые значения.
Когда имя параметра определено, за ним может следовать знак "?", означающий, что оно нечувствителено к регистру, аналогично имени в символе-идентификаторе. Не должно быть пробелов между именем и "?". За определением параметра также может следовать знак "*", чтобы указать, что для него требуется значение, которое не является пустым, либо знак ":", за которым следует значение по умолчанию, которое назначается параметру вместо пустого, если никакое другое значение не предоставляется.
macro prepare name*,value:0
  name = value
end macro

prepare x       ; x = 0
prepare y,1     ; y = 1
Если аргумент макроинструкции должен содержать символ запятой, весь аргумент должен быть окружён знаками  "<" и ">" (они не становятся частью значения). Если внутри такого значения встречается другой символ "<", он должен быть "уравновешен" соответствующим символом ">" внутри того же значения.
macro data name,value
  name:
  .data db value
  .end:
end macro

data example, <'abc',10>
За последним определённым параметром может следовать знак "&", чтобы указать, что этому параметру должно быть присвоено значение, содержащее всю оставшуюся часть исходной строки, даже если она обычно определяет несколько аргументов. Поэтому, когда макроинструкция имеет только один параметр, за которым следует "&", значением этого параметра будет полный список аргументов, следующих за инструкцией.
macro id first,rest&
  dw first
  db rest
end macro

id 2, 7,1,8
Когда конкретное имя параметра заменяется его значением и при этом это имя предварено знаком "`" (без пробелов между ними), текст значения вставляется в строку в кавычках, и эта строка заменяет знак "`" и название параметра вместе.
macro text line&
  db `line
end macro

text x+1        ; db 'x+1'
"local" — команда, которая может использоваться только внутри макроинструкции. За ней должно следовать одно или несколько имён, разделённых запятыми. Она объявляет, что имена из этого списка в контексте текущей макроинструкции должны интерпретироваться как принадлежащие специальному пространству имён, связанному с этой макроинструкцией, вместо текущего базового пространства имён. Это позволяет создавать уникальные символы каждый раз, когда вызывается макроинструкция. Такое объявление определяет дополнительные параметры с указанными именами, и поэтому оно влияет только на использование тех имён, которые следуют в пределах той же макроинструкции. Объявление одного и того же имени локальным несколько раз в одной и той же макроинструкции не даёт дополнительного эффекта.
macro measured name,string
  local top
  name db string
  top: name.length = top - name
end macro

measured hello, 'Hello!'        ; hello.length = 6 
Символ, локальный в макроинструкции, никогда не считается самой последней текущей меткой, которая является базовой для символов, начинающихся с точки. Кроме того, его пространство имён потомков отделено от основного дерева символов, поэтому если использовалась команда "namespace" с локальным символом в качестве её аргумента, символы из главного дерева больше не будут видны (включая все именованные инструкции ассемблера, даже такие команды, как "end namespace").
Так же как и символ в выражении может быть переопределён и в новом определении может ссылаться на своё предыдущее значение, так и макроинструкции могут быть переопределены и могут использовать предыдущее значение этого символа инструкции в своем тексте:
macro zero
  db 0
end macro

macro zero name
  label name:byte
  zero
end macro

zero x
И так же, как и другие символы, макроинструкция может иметь ссылку вперёд, если она определена ровно один раз во всем исходнике.
Команда "purge" отбрасывает определение символа точно так же, как "restore", но делает это для символа класса инструкций. В остальном она действует так же как и "restore". Макроинструкция может удалить своё собственное определение с помощью "purge".
Макроинструкция может использовать своё собственное значение рекурсивным способом, но во избежание непреднамеренной бесконечной рекурсии эта возможность доступна только в том случае, когда макроинструкция помечена знаком ":" следующим за её идентификатором.
macro factorial: n
  if n
    factorial n-1
    result = result * (n)
  else
    result = 1
  end if
end macro
Помимо разрешения рекурсии, такая макроинструкция ведёт себя как константа. Она не может быть переопределена и к ней нельзя применить "purge".
Макроинструкция, в свою очередь, может определять другую макроинструкцию или несколько. Блоки, обозначенные с помощью "macro" и "end macro", должны быть правильно вложены друг в друга, чтобы такое определение было принято ассемблером.
macro enum enclosing
  counter = 0
  macro item name
    name := counter
    counter = counter + 1
  end macro
  macro enclosing
    purge item,enclosing
  end macro
end macro

enum x
  item a
  item b
  item c
x
Когда требуется, чтобы макроинструкция генерировала непарную команду "macro" или "end macro", это можно сделать с помощью специальной инструкции "esc". Её аргумент становится частью макроинструкции, но не учитывается при подсчёте вложенных пар "macro" и "end macro".
macro xmacro name
  esc macro name x&
end macro

xmacro text
  db `x
end macro
Когда за идентификатором макроинструкции в его определении следует знак "!", он определяет безусловную макроинструкцию. Это особый вид символа класса инструкций, который означивается даже в местах, где сборка приостановлена ​​— как внутри условного блока, условие которого ложно, или внутри определения другой макроинструкции. Это позволяет определить инструкции, которые можно использовать, если в противном случае потребовалось бы указать непосредственно "end if" или "end macro", как в следующем примере:
macro proc name
  name:
  if used name
end macro

macro endp!
  end if
  .end:
end macro

proc tester
  db ?
endp
Если бы макроинструкция "endp" в вышеприведённом примере не была определена как безусловная, а блок, начинающийся с "if", пропускался, макроинструкция не означивалась бы, и это привело бы к ошибке, поскольку "end if" отсутствовало бы.
Следует отметить, что команда "end" выполняет инструкцию, идентифицируемую её аргументом в дочернем пространстве имён нечувствительного к регистру символа "end". Следовательно, такая команда как "end if" может альтернативно вызываться с идентификатором "end.if", и можно переопределить любую такую ​​инструкцию, переопределив символ в пространстве имён относящемся к "end?". Более того, любая инструкция определённая в пространстве имён "end?" может быть вызвана командой "end". Этот слегка измененный вариант приведенного выше примера использует эту возможность:
macro proc name
  name:
  if used name
end macro

macro end?.proc!
  end if
  .end:
end macro

proc tester
  db ?
end proc
Аналогичное правило применяется к команде "else" и инструкциям в пространстве имён "else?".
Когда идентификатор состоящий из одного знака "?" используется в качестве символа инструкции в определении макроинструкции, он определяет специальную инструкцию, которая затем вызывается для каждой из последующих исходных строк, подлежащих сборке и не содержащих безусловных инструкций. При этом полный текст строки становится аргументами этой макроинструкции. Этот специальный символ также может быть определён как безусловная инструкция, и затем он вызывается для каждой следующей строки без исключения. Это позволяет полностью отменить сборку фрагмента текста. В следующем примере определяется макроинструкция, которая позволяет определить блок комментариев, с пропуском всех исходных строк текста, пока не встретится строка с содержимым, равным аргументу макроинструкции "comment".
macro comment? ender
  macro ?! line&
    if `line = `ender
      purge ?
    end if
  end macro
end macro

comment ~
  Any text may follow here.
~

9. Помеченные макроинструкции


Команда "struc" позволяет определить помеченную инструкцию в виде макроинструкции. За исключением того факта, что такое определение должно завршаться командой "end struc" вместо "end macro", эти макроинструкции определяются так же, как и с командой "macro". Помеченная инструкция означивается, когда первый идентификатор команды не является инструкцией, а второй идентификатор относится к классу помеченных инструкций:
struc some
  db 1
end struc

get some        ; здесь будет собрано "db 1"
Внутри помеченных макроинструкций, идентификаторы начинающиеся с точки больше не ссылаются на пространство имён последней определённой обычной метки. Вместо этого они ссылаются на пространство имён метки, которой была помечена инструкция.
struc POINT
  label . : qword
  .x dd ?
  .y dd ?
end struc

my POINT        ; определяет my.x and my.y
Обратите внимание, что родительский символ, на который можно ссылаться с помощью идентификатора ".", не определён, если макроинструкция не сгенерирует соответствующее определение. Кроме того, этот символ не считается самой последней меткой в окружающем пространстве имён, если он не определён как актуальная метка в макроинструкции, которую он помечает. Для более лёгкого использования этой возможности, с помощью макроинструкций могут быть определены другие синтаксисы, как в следующем примере:
macro struct? definition&
  struc definition
    label . : .%top - .
    namespace .
end macro

macro ends?!
      %top:
    end namespace
  end struc
end macro

struct POINT vx:?,vy:?
  x dd vx
  y dd vy
ends

my POINT 10,20
Обратите внимание на то, что нет необходимости использовать команду "esc" для генерации непарных "struc" или "end struc" с помощью макроинструкции, определённой через "macro". Таким же образом непарный "macro" или "end macro" может свободно встречаться в определении, сделанном с помощью "struc".
Команда "restruc" аналогична команде "purge", но она работает с символами из класса помеченных инструкций.
Как и в случае с "macro", с командой "struc" можно использовать идентификатор, состоящий из единственного знака "?". Он определяет специальную помеченную макроинструкцию, которая вызывается каждый раз, когда первый символ исходной строки не распознаётся как инструкция. Всё, что следует за первым идентификатором, становится аргументами для помеченной макроинструкции. В следующем примере эта функция используется для отлова любых потерянных меток (тех, за которыми не следует ни один символ) и обработки их как обычных вместо того, чтобы вызывать ошибку. Это достигается путем установки с помощью знака ":" значения по умолчанию для параметра "def":
struc ? def::&
  . def
end struc

orphan
regular:
assert orphan = regular
Подобно "macro", этот специальный вариант не отменяет безусловные помеченные инструкции, если он сам не является безусловным.
В то время как "." обеспечивает эффективный метод доступа к символу метки, иногда это может потребоваться для обработки фактического текста метки. Для этого можно определить специальный параметр, и его имя должно быть заключено в скобки перед именем помеченной макроинструкции:
struc (name) SYMBOL
  . db `name,0
end struc

test SYMBOL

10. Символьные переменные


"equ" — это встроенная помеченная инструкция, которая определяет символ класса выражений с символьным значением. Такое значение может содержать любой текст (даже пустой), и когда оно используется в выражении, оно эквивалентно вставке текста его значения вместо его идентификатора с тем же эффектом, что и при означивании параметра макроинструкции.
Это может привести к результатам, отличным от получаемых при использовании стандартной переменной, определенной с помощью "=", как показано в следующем примере:
numeric = 2 + 2
symbolic equ 2 + 2
x = numeric*3           ; x = 4*3
y = symbolic*3          ; y = 2 + 2*3
В то время как переменной "x" присваивается значение 12, значением "y" будет 8. Это показывает, что использование таких символов может привести к нежелательным эффектам, и поэтому определений этого типа следует избегать, если этого на самом деле не требуется.
"equ" допускает переопределения и сохраняет предыдущее значение символа аналогично команде "=:", поэтому более раннее значение можно вернуть с помощью инструкции "restore". Чтобы заменить символьное значение (аналогично тому, как "=" перезаписывает обычное значение), вместо "equ" должна использоваться команда "reequ".
Символьное значение, помимо сохранения точного текста, с которым оно было определено, сохраняет контекст, в котором символы, содержащиеся в этом тексте, должны интерпретироваться. Следовательно, оно может стать надёжной ссылкой на значение какого-либо другого символа, даже если он используется в другом контексте (включая изменение базового пространства имён или символа, на который указывает начальная точка):
first:
  .x = 1
  link equ .x
  .x = 2
second:
  .x = 3
  db link         ; db 2
Следует отметить, что то же самое относится к параметрам любой макроинструкции. Если во время выполнения макроинструкции контекст изменяется, идентификаторы в тексте параметра по-прежнему ссылаются на те же символы, что и в строке, которая вызвала эту инструкцию:
x = 1
namespace x
  x = 2
end namespace
macro prodx value
  namespace x
    db value*x
  end namespace
end macro
prodx x         ; db 1*2
Кроме того, параметры, определённые с помощью команды "local", используют тот же механизм для изменения контекста, в котором интерпретируется данное имя, без изменения текста имени. Однако такой изменённый контекст несущественен, если значение параметра вставлено в середину или в конец сложного идентификатора, поскольку именно структура идентификатора определяет, как интерпретируются его более поздние части, и значение имеет контекст лишь изначальной части.
Если текст после "equ", рассматриваемый как последовательность символов, содержит какие-либо идентификаторы известных символьных значений, каждый из них заменяется текстом его значения перед созданием нового — определяемого идентификатора. Соответствующие разделы текста по-прежнему сохраняют исходный контекст.
"define" — это обычная инструкция, которая также создаёт символьное значение, но в отличие от "equ" она не означивает символьные переменные в назначенном тексте. За ней должен следовать идентификатор определяемого символа, а затем текст значения.
Различие между "equ" и "define" часто незаметно, поскольку при использовании в конечном выражении символьные переменные вложенно означиваются до тех пор, пока не останутся только используемые компоненты выражений. "define" можно использовать для создания ссылки на другую символьную переменную, как показано в следующем примере:
a equ 0*
x equ -a
define y -a
a equ 1*
db x 2          ; db -0*2
db y 2          ; db -1*2
Другое использование "define" показано в последующих разделах, с введением других инструкций, которые оперируют символьными значениями.
"define", как и "equ", сохраняет предыдущее значение символа. "redefine" является вариантом инструкции, которая отбрасывает более раннее значение, аналогично "reequ".
Обратите внимание, что хотя символьные переменные относятся к классу выражений, их состояние не может быть определено с помощью таких операторов, как "defined", "definite" или "used", поскольку логическое выражение означивается так, как если бы каждая символьная переменная была заменена текстом соответствующего значения. Поэтому оператор, за которым следует идентификатор символьной переменной, будет применён к содержимому этой переменной, каким бы оно ни было. Например, если создаётся символьная переменная, которая является ссылкой на обычный символ, то любой оператор, такой как "defined", за которым следует идентификатор упомянутой символьной переменной, будет определять состояние связанного символа, а не этой символьной переменной.

11. Повторяющиеся блоки инструкций


Инструкция "repeat" позволяет собирать блок инструкций несколько раз, причём количество повторений определяется значением его аргумента. Блок инструкций должен заканчиваться командой "end repeat". Синоним "rept" можно использовать вместо "repeat".
a = 2
repeat a + 3
  a = a + 1
end repeat
assert a = 7
Инструкция "while" собирает блок инструкций многократно, если условие, указанное в её аргументе, истинно. Её аргумент должен быть логическим выражением, подобным аргументу "if" или "assert". Блок должен завершаться командой "end while".
a = 7
while a > 4
  a = a - 2
end while
assert a = 3
"%" — это специальный параметр, который предварительно обрабатывается внутри повторяемого блока инструкций и заменяется десятичным числом — номером текущего повторения (начиная с 1). Он работает аналогично параметру макроинструкции, поэтому он заменяется его значением до обработки основной команды, и поэтому его можно использовать для создания идентификаторов символов, содержащих в имени число:
repeat 16
  f#% = 1 shl %
end repeat
Вышепривёденный пример определяет символы от "f1" до "f16" со значениями, равными последовательным степеням двойки.
Инструкция "repeat" может иметь дополнительные аргументы, разделённые запятыми, каждый из которых содержит имя дополнительного параметра, специфичного для этого блока. За каждым именем может следовать знак ":" и выражение, указывающее базовое значение параметра, с которого начинается отсчёт повторений. Это позволяет легко изменить предыдущий пример, чтобы определить диапазон символов от "f0" до "f15":
repeat 16, i:0
  f#i = 1 shl i
end repeat
"%%" — это ещё один специальный параметр, значение которого равно общему количеству запланированных повторений. Этот параметр не определён внутри блока "while". В следующем примере он используется для создания последовательности байтов со значениями по убыванию от 255 до 0:
repeat 256
  db %%-%
end repeat
Инструкция "break" позволяет преждевременно прервать повторение. Когда встречается эта инструкция, пропускается оставшаяся часть повторяющегося блока и дальнейшие повторения не выполняются. Её можно использовать для прерывания повторения, если выполняется определённое условие:
s = x/2
repeat 100
  if x/s = s
    break
  end if
  s = (s+x/s)/2
end repeat
Приведённый выше пример извлекает квадратный корень из значения символа "x", которое предполагается определённым в другом месте. Его можно легко переписать для той же задачи с помощью "while" вместо "repeat":
s = x/2
while x/s <> s
  s = (s+x/s)/2
  if % = 100
    break
  end if
end while
Инструкция "iterate" (с синонимом "irp") повторяет блок инструкций, проходя по списку значений, разделённых запятыми. Первым аргументом "iterate" должно быть имя параметра, затем запятая, а затем список значений. Во время каждой итерации параметр получает одно из значений из списка.
iterate value, 1,2,3
  db value
end iterate
Как и в случае аргумента макроинструкции, значение параметра, содержащего запятые, должно быть окружено знаками "<" и ">". Также возможно заключить первый аргумент инструкции "iterate" между "<" и ">", чтобы определить несколько параметров. Следующий далее список значений делится на секции, содержащие столько значений, сколько и параметров, и каждое повторение работает с одной такой секцией, присваивая каждому параметру соответствующее значение:
iterate <name,value>, a,1, b,2, c,3
  name = value
end iterate
За именем параметра, как и в случае макроинструкций, также может следовать знак "*", требующий, чтобы параметр имел непустое значение, или ":" для значения по умолчанию.
Инструкция "break" и параметры "%" и "%%" могут использоваться внутри блока "iterate" с теми же эффектами, что и в случае "repeat".
"indx" — это инструкция, которую можно использовать только внутри повторяемого блока, она заменяет значения всех итерируемых параметров на значения этих же параметров, соответствующие итерации с номером, указанным в аргументе "indx" (однако в начале следующей итерации параметры принимают свои обычные значения). Это позволяет обрабатывать повторяющиеся значения в ином порядке. В следующем примере значения обрабатываются от последнего к первому:
iterate value, 1,2,3
  indx 1+%%-%
  db value
end iterate
С помощью "indx" можно даже несколько раз сменить представление итерируемых значений в течение одного повторения. В следующем примере вся обработка выполняется во время первого повторения повторяющегося блока, а затем используется инструкция "break", чтобы пропустить дальнейшие итерации:
iterate str, 'alpha','beta','gamma'
  repeat %%
    dw offset#%
  end repeat
  repeat %%
    indx %
    offset#% db str
  end repeat
  break
end iterate
Параметры, определённые с помощью "iterate", не присоединяют контекст к итерируемым значениям, но и не удаляют исходный контекст, если он уже прикреплён к тексту аргументов. Таким образом, если значения, заданные для "iterate", сами были созданы из другого параметра, который сохранил исходный контекст для символа-идентификаторов (например, для параметра макроинструкции), то этот контекст сохраняется. В противном же случае "iterate" определяет подстановку лишь чистого текста.
Параметры, определённые с помощью инструкций, таких как "iterate" или "repeat", обрабатываются повсеместно в тексте соответствующего блока, но с некоторыми ограничениями, если блок определяется частично текстом макрокоманды и частично в других местах. В этом случае параметры доступны только в тех частях блока, которые определены в том же месте, что и исходная команда.
В определении параметра к его имени может быть присоединён знак "?", чтобы указать, что этот параметр не чувствителен к регистру. Однако когда параметры распознаются внутри предварительно обрабатываемой строки, не имеет значения, следует ли за ними "?". Единственный модификатор, который распознаётся препроцессором, когда он заменяет параметр своим значением, это знак "`".
Повторяющие инструкции вместе с "if" принадлежат группе, называемой управляющими директивами. Это инструкции, которые управляют процессом сборки. Каждая из них определяет свой собственный блок подчинённых инструкций, закрывающися соответствующей командой "end", и если эти блоки вложены друг в друга, это вложение должно быть правильным — внутренний блок должен закрываться до внешнего. Таким образом, все управляющие директивы являются безусловными инструкциями — они распознаются, даже если находятся внутри пропускаемого блока.
"postpone" — это ещё одна управляющая директива, которая откладывает сборку блока инструкций до момента, когда весь последующий исходный текст будет уже обработан.
dw final_count
postpone
  final_count = counter
end postpone
counter = 0
Приведённый выше пример откладывает определение символа "final_count" до тех пор, пока не будет обработан весь исходник, чтобы он мог получить доступ к окончательному значению переменной "counter".
Сборка исходного текста, который следует за "postpone", включает сборку любых дополнительных блоков, объявленных с помощью "postpone", поэтому, если таких блоков несколько, они собираются в обратном порядке. Тот блок, который был объявлен последним, собирается первым, когда будет достигнут конец исходного текста.
Если директиве "postpone" предоставляется аргумент, состоящий из одинственного знака "?", это указывает ассемблеру, что блок содержит операции, которые не должны влиять ни на одно из значений, определённых в главном исходнике, и, таким образом, ассемблер может воздерживаться от их означивания до тех пор, пока все остальные значения не будут успешно разрешены. Такие блоки обрабатываются даже позже, чем те, которые объявлены с помощью "postpone" без аргументов. Они могут использоваться для выполнения некоторых завершающих задач, таких как вычисление контрольной суммы собранного кода.
"irpv" — ещё одна повторяющая инструкция и итератор. У неё всего два аргумента, первый — имя параметра, а второй — идентификатор переменной. Она проходит по всем сохранённым значениям символьной переменной, начиная с самого старого (это применяется только к значениям, определённым ранее в исходнике).
var equ 1
var equ 2
var equ 3
var reequ 4
irpv param, var
  db param
end irpv
В приведённом выше примере — три итерации, со значениями 1, 2 и 4.
"irpv" может преобразовать значение символьной переменной в параметр. Это может быть полезно, поскольку символьная переменная означивается только в выражениях внутри аргументов инструкций (помеченных или нет), в то время как параметры обрабатываются предварительно во всей исходной строке до начала какой-либо обработки команды. Это позволяет, например, переопределить обычное значение, которое связано символьной переменной:
x = 1
var equ x
irpv symbol, var
  indx %%
  symbol = 2
  break
end irpv
assert x = 2
Сочетание "indx" и "break" было добавлено ​​в вышеприведённый пример, чтобы ограничить итерацию самым последним значением символьной переменной. В следующем разделе будет представлено более хорошее решение той же задачи.
Если переменная, переданная "irpv", имеет значение не являющееся символьным, параметру присваивается текст, который выдаёт то же значение при вычислении. Когда значение является положительным числом, параметр заменяется его десятичным представлением (аналогично тому, как обрабатывается параметр "%"), в противном случае параметр заменяется идентификатором символа-посредника, содержащего значение из стека сохранений.

12. Сопоставление параметров


"match" — это управляющая директива, которая вызывает сборку блока инструкций только в том случае, если текст, указанный во втором аргументе, соответствует шаблону, заданному первым. Текст отделяется от шаблона запятой и включает в себя всё, что следует за этим разделителем до конца строки.
Каждый специальный знак (за исключением знаков "," и "=", которые имеют специальный смысл в шаблоне) сопоставляется буквально — ему должен быть поставлен в соответствие идентичный токен в тексте. В следующем примере содержимое первого блока собирается, а содержимое второго — нет.
match +,+
  assert 1        ; сопоставление принято
end match

match +,-
  assert 0        ; сопоставление отвергнуто
end match
Строки в кавычках также сопоставляются буквально, но именные токены в шаблоне обрабатываются иначе. Каждое имя действует как подстановочный символ и может сопоставиться с любой последовательностью токенов, которая не является пустой. Если сопоставление принято, создаются параметры с такими именами, и каждому присваивается значение, равное тексту, с которым сопоставился подстановочный символ.
match a[b], 100h[3]
  dw a+b          ; dw 100h+3
end match
Имя параметра в шаблоне может иметь дополнительный знак "?", присоединённый к нему, указывающий, что это имя нечувствительно к регистру.
Символ "=" требует, чтобы следующий за ним токен сопоставлялся буквально. Это позволяет выполнять сопоставление именных токенов, а также специальных знаков, которые, следуя за именем, в противном случае имели бы другой смысл, например ",", "=", или "?".
match =a==a, a=8
  db a            ; db 8
end match
Если за "=" следует именной токен с присоединённым к нему знаком "?" этот элемент сопоставляется буквально, но без учёта регистра:
match =a?==a, A=8
  db a            ; db 8
end match
Если в шаблоне много подстановочных символов, каждый последующий имеет всё меньше количество доступных для сопоставления токенов, а последний принимает для сопоставления всё, что осталось. Если следующие друг за другом подстановочные символы не имеют между собой общих (буквально совпадающих) элементов, то первый сопоставляется только с одним токеном, а второй — с оставшимся текстом:
match car cdr, 1+2+3
  db car          ; db 1
  db cdr          ; db +2+3
end match
В приведённом выше примере сопоставляемый текст должен содержать как минимум два токена, поскольку для каждого подстановочного символа требуется, чтобы хотя бы один токен был непустым. В следующем примере есть дополнительные ограничения, но применяются те же общие правила, и первый подстановочный символ поглащает как можно меньше:
match first:rest, 1+2:3+4:5+6
  db `first       ; db '1+2'
  db 13,10
  db `rest        ; db '3+4:5+6'
end match
Хотя любые пробелы после подстановочного символа игнорируются, наличие или отсутствие пробелов между буквально сопоставляемыми элементами значимо. Если между такими элементами нет пробелов, то и сопоставленные им части текста также не должны разделяться пробелами. Но если между элементами в образце есть пробел, это не накладывает никаких ограничений на использование пробелов в соответствующем тексте — их может не быть.
match ++,++
  assert 1        ; сопоставление принято
end match

match ++,+ +
  assert 0        ; сопоставление отвергнуто
end match

match + +,++
  assert 1        ; сопоставление принято
end match

match + +,+ +
  assert 1        ; сопоставление принято
end match
Наличие пробела в тексте становится обязательным, если шаблон содержит знак "=", за которым следует пробел:
match += +, ++
  assert 0        ; сопоставление отвергнуто
end match

match += +, + +
  assert 1        ; сопоставление принято
end match
Команда "match" аналогична "if" тем, что она позволяет использовать "else" или "else match" для организации выбора блоков, из которых выполняется только один:
macro let param
  match dest+==src, param
    dest = dest + src
  else match dest-==src, param
    dest = dest + src
  else match dest++, param
    dest = dest + 1
  else match dest--, param
    dest = dest + 1
  else match dest==src, param
    dest = src
  else
    assert 0
  end match
end macro

let x=3                 ; x = 3
let x+=7                ; x = x + 7
let x++                 ; x = x + 1
Можно даже смешивать условия "if" и "match" в последовательности блоков "else". Вся конструкция должна завершаться командой "end", соответствующей той из них, которая использовалась последней:
macro record text
  match any, text
    recorded equ `text
  else if RECORD_EMPTY
    recorded equ ''
  end if
end macro 
"match" может распознавать символьные переменные, и перед началом сопоставления их идентификаторы в тексте второго аргумента заменяются соответствующими значениями (так же, как они заменяются в тексте, который следует за командой "equ"):
var equ 2+3

match a+b, var
  db a xor b
end match
Это означает, что "match" может использоваться вместо "irpv" для преобразования последнего значения символьной переменной в параметр. Пример из предыдущего раздела, где "irpv" использовался с "break" для выполнения только одной итерации с последним значением, может быть переписан с использованием "match":
x = 1
var equ x
match symbol, var
  symbol = 2
end match
assert x = 2
Разница между ними заключается в том, что "irpv" выполнит свой блок даже для пустого значения, в то время как для "match", для обработки пустого теста, необходимо добавить блок "else".
Если означивание символьных переменных в сопоставляемом тексте нежелательно, можно использвать символ, созданный с помощью "define", в качестве посредника для сохранения текста, так как замена нерекурсивна:
macro drop value
  local temporary
  define temporary value
  match =A, temporary
    db A
    restore A
  else
    db value
  end match
end macro

A equ 1
A equ 2

drop A
drop A
Может возникнуть проблема, связанная с тем, что "define" может изменить смысл текста, снабдив его локальным контекстом. Но когда значение для "define" берётся из параметра макроинструкции (как в приведённом выше примере), оно уже приносит свой исходный контекст, и "define" не меняет его.
Директива "rawmatch" (с синонимом "rmatch") очень похожа на "match", но она работает с необработанным текстом второго аргумента. Она не только не означивает символьные переменные, но также обнажает текст любого дополнительного контекста, который этот параметр может нести.
struc has instruction
  rawmatch text, instruction
    namespace .
      text
    end namespace
  end rawmatch
end struc

define x
x has a = 3
assert x.a = 3
В приведённом выше примере идентификатор "a" интерпретировался бы в контексте, действительном для исходной строки, вызывающей макроинструкцию "has", если бы он не был преобразован обратно в необработанный текст с помощью "rmatch".

13. Области выдачи


Инструкция "org" начинает новую область выдачи. Содержимое такой области записывается в целевой файл следом за предыдущими данными, но адреса в новой области основаны на значении, указанном в аргументе "org". Область закрывается автоматически, когда начинается следующая облать выдачи а также в конце исходника.
org 100h
start:                  ; start = 100h
"$" — это встроенный символ класса выражений, который всегда равен значению текущего адреса. Поэтому определение константы со значением, указанным как "$", эквивалентно определению метки в той же точке:
org 100h
start = $               ; start = 100h
Символ "$$" всегда равен базовому адресу текущего адресного пространства, поэтому в области, начинающейся с "org", его значение совпадает с базовым адресом в аргументе этой инструкции "org". Таким образом, разница между "$" и "$$" равна адресу текущей позиции относительно начала области:
org 2000h
db 'Hello!'
size = $ - $$           ; size = 6
Символ "$@" равен базовому адресу текущего блока неинициализированных данных. Если нет таких данных, определённых непосредственно перед текущей позицией, это значение совпадает с "$", в противном случае оно равно "$" минус длина указанных данных внутри текущего адресного пространства. Обратите внимание, что зарезервированные данные больше не являются таковыми, если за ними следуют инициализированные данные.
Инструкция "section" похожа на "org", но она дополнительно урезает все предшествующие ей зарезервированные данные, подобно тому, как неинициализированные данные не записываются в выход, если они находятся в конце исходного файла. Поэтому, за инструкцией "section" могут следовать определения инициализированных данных, и это не приводит к инициализации предшествующих инструкции "section" зарезервированных данных нулями и записи их в выход. В этом примере только первый из трёх зарезервированных буферов фактически преобразуется в обнулённые данные и записывается в выход, поскольку за ним следуют некоторые инициализированные данные. Второй урезается из-за "section", а третий урезается, так как лежит в конце исходного файла:
data1 dw 1
buffer1 rb 10h          ; инициализируется нулями и попадает в выход

org 400h
data dw 2
buffer2 rb 20h          ; не попадает в выход

section 1000h
data3 dw 3
buffer3 rb 30h          ; не попадает в выход
"$%" — это встроенный символ, равный смещению в выходном файле, по которому будут сгенерированы инициализированные данные, если они были определены в этой точке. Символ "$%%" — это текущее смещение в выходном файле. Эти два значения отличаются, только если они используются после того, как некоторые данные были зарезервированы — тогда "$%" будет больше, чем "$%%", на длину неинициализированных данных, которые будут сгенерированы в выходе, если за ними последуют некоторые инициализированные данные.
db 'Hello!'
rb 4
position = $%%          ; position = 6
next = $%               ; next = 10
Значения в комментариях приведённого выше примера предполагают, что исходник не содержит других инструкций, генерирующих выходные данные.
Команда "virtual" создаёт специальную область выдачи, которая не записывается в основной выходной файл. Область этого вида должна находиться между командами "virtual" и "end virtual", и после её закрытия генератор выхода возвращается в область, в которой он работал ранее, с такими же позицией и адресом, какие были до открытия блока "virtual". Это позволяет также вкладывать блоки "virtual" друг в друга.
Когда у "virtual" нет аргумента, базовый адрес этой области совпадает с текущим адресом во внешней области. Аргумент команды "virtual" может иметь форму ключевого слова "at", за которым следует выражение, определяющее базовый адрес для вложенной области:
int dw 1234h
virtual at int
  low db ?
  high db ?
end virtual
Вместо или в дополнение к такому аргументу за "virtual" может также следовать ключевое слово "as" и строка, определяющая расширение дополнительного файла, в котором будет сохранено инициализированное содержимое этой области в конце сборки, если она пройдёт успешно.
Инструкция "load" определяет значение переменной, загружая строку байтов из данных, сгенерированных в области выдачи. За ней должен следовать идентификатор определяемого символа, затем опционально — знак ":" и количество загружаемых байтов, далее ключевое слово "from" и адрес загружаемых данных. Этот адрес может быть указан в двух режимах. Если это просто числовое выражение, это адрес в текущей области. В этом случае загруженные байты должны быть уже сгенерированы, поэтому загрузка возможна только из пространства между адресами "$$" и "$".
virtual at 100h
  db 'abc'
  load b:byte from 101h   ; b = 'b'
end virtual
Если количество байтов не указано, длина загружаемой строки определяется размером, связанным с адресом.
Другой вариант "load" требует метку специального вида, которая создается с помощью "::" вместо ":". Такая метка имеет значение, которое нельзя использовать напрямую, но её можно использовать с инструкцией "load" для доступа к данным в области, в которой эта метка была определена. Адресом для "load" должна быть указана метка области, за которой следует ":", а затем адрес в этой области:
virtual at 0
  hex_digits::
  db '0123456789ABCDEF'
end virtual
load a:byte from hex_digits:10  ; a = 'A'
Этот вариант "load" может получить доступ к данным, которые генерируются позже, даже в пределах текущей области:
area::
db 'abc'
load sub:3 from area:$-2        ; sub = 'bcd'
db 'def'
Инструкция "store" может модифицировать уже сгенерированные в области выдачи данные. За ней должно следовать значение (автоматически преобразуемое в строку байтов), затем опционально знак ":", за которым следует число записываемых байтов (если этот параметр отсутствует, длина строки определяется размером, связанным с адресом), далее ключевое слово "at" и адрес заменяемых данных — в одном из тех же двух режимов, которые разрешены для "load". Однако инструкции "store" запрещено изменять данные, которые ещё не были сгенерированы. Любая область, которая была затронута инструкцией "store", становится переменной областью, из которой также запрещается опережающее чтение данных иструкцией "load".
В следующем примере используется комбинация "load" и "store" для шифрования всего содержимого текущей области с помощью простой операции "xor":
db "Text"
key = 7Bh
repeat $-$$
  load a : byte from $$+%-1
  store a xor key : byte at $$+%-1
end repeat
Если окончательные данные области, которая была изменена инструкцией "store", необходимо прочитать в исходнике раньше, этого можно добиться путём копирования этих данных в другую область, на которую не наложены такие ограничения. Это аналогично определению константы с окончательным значением некоторой переменной:
load char : byte from const:0

virtual
  var::
  db 'abc'
  .length = $
end virtual

store 'A' : byte at var:0

virtual
  const::
  repeat var.length
    load a : byte from var:%-1
    db a
  end repeat
end virtual 
На метку области может ссылаться вперёд инструкция "load", но на неё никогда не может ссылаться вперёд инструкция "store", даже если эта метка ссылается на текущую область выдачи.
Инструкция "virtual" в качестве единственного аргумента может иметь существующую метку области. Этот вариант позволяет расширить ранее определённый и закрытый блок дополнительными данными. Метка области должна ссылаться на блок, который был ранее создан в исходнике с помощью инструкции "virtual". Любое определение данных в расширяемом блоке будет иметь такой же эффект, как если бы это определение присутствовало в исходном блоке "virtual".
virtual at 0 as 'log'
  Log::
end virtual

virtual Log
  db 'Hello!',13,10
end virtual
Если в выражении используется метка области, она образует переменный член линейного многочлена. Метаданными такого члена является строка "::", позволяющая определить, какая метка области использовалась для формирования значения, поскольку метаданные членов, созданных с помощью "element", всегда являются числовыми.
Существует дополнительные варианты директив "load" и "store", которые позволяют считывать и изменять уже сгенерированные данные в выходном файле, опираясь на смещение в этом выходе. Эти варианты распознаются, когда после ключевого слова "at" или "from" следует знак ":", а затем значение смещения.
checksum = 0
repeat $%
  load a : byte from : %-1
  checksum = checksum + a
end repeat
Инструкция "restartout" отбрасывает все выходные данные, сгенерированные до этого момента, и генерация начинается заново. Необязательный аргумент может указывать базовый адрес области вывода, генерирование которй начинается заново. Если у "restartout" нет аргументов, базовым адресом для новой области становится текущий адрес.
Инструкции "org", "section" и "restartout" нельзя использовать внутри блока "virtual", они только могут разделять области, которые войдут в выходной файл.

14. Прочие инструкции


Инструкция "include" читает исходный текст из другого файла и обрабатывает его, прежде чем продолжить работу в текущем исходнике. Её аргумент должен быть строкой, указывающей путь к файлу (формат пути может зависеть от операционной системы). Если после инструкции и перед аргументом добавляется знак "!", этот файл читается и обрабатывается безусловно, даже если он находится внутри пропускаемого блока (безусловные инструкции из этого файла могут быть  затем распознаны).
include 'macro.inc'
Инструкция "eval" принимает последовательность байтов, определяемую ее аргументами, интерпретирут её как исходный текст и собирает её. Аргументы — это либо строки, либо числовые значения отдельных байтов, разделённые запятыми. В следующем примере "eval" используется для генерации определений символов, названных последовательными буквами алфавита:
repeat 26
  eval 'A'+%-1,'=',`%
end repeat

assert B = 2
Команда "display" записывает последовательность байтов в стандартный вывод рядом с сообщениями, генерируемыми ассемблером. За ней должны следовать строки или числовые значения отдельных байтов, разделённые запятыми. В следующем примере "repeat 1" используется для определения параметра с десятичным представлением вычисляемого числа, а затем отображает его в виде строки:
macro show description,value
  repeat 1, d:value
    display description,`d,13,10
  end repeat
end macro

show '2^64=',1 shl 64
Инструкция "err" сигнализирует об ошибке в процессе сборки, а её аргументом указывается пользовательское сообщение. Она допускает аргументы такого же вида, что и директива "display".
if $>10000h
  err 'segment too large'
end if
Директива "format" позволяет настроить параметры вывода. Единственный доступный вариант — "format binary", за которым следует ключевое слово "as" и строка, определяющая расширение выходного файла. Если имя выходного файла не указано в командной строке, оно создаётся из пути к основному исходному файлу путём удаления расширения и присоединения нового расширения, если оно определено.
format binary as 'com'
Директива "format", аналогично "end", использует идентификатор, следующий за ней, чтобы найти инструкцию в дочернем пространстве имён нечувствительного к регистру символа с именем "format". Единственной встроенной инструкцией, которая находится в этом пространстве имён, является "binary", но в виде макроинструкций могут быть определены дополнительные.
Встроенный символ "%t" имеет постоянное значение временной отметки, указывающей момент времени, когда была начата сборка.
"__file__" — это встроенный символ, значением которого является строка, содержащая имя обрабатываемого в данный момент исходного файла. Сопутствующий символ "__line__" указывает номер обрабатываемой строки в этом файле.
Директива "outscope" доступна во время обработки любой макроинструкции, и она изменяет команду, которая следует за ней в той же строке. Если команда инициирует определение каких-либо параметров, то они создаются не в контексте обрабатываемой в данный момент макроинструкции, а в контексте исходного текста, который её вызвал.
macro match?! statement&
  display 'match wrapper'
  outscope match statement
end macro
Это позволяет не только безопасно "обернуть" некоторые управляющие директивы в макроинструкции, но и создать дополнительные пользовательские языковые конструкции, которые определяют параметры для блока текста.
Директива "retaincomments" переключает ассемблер в режим, в котором точка с запятой обрабатывается как обычный токен и, следовательно, комментарии не удаляются из строк перед обработкой. Это позволяет использовать точки с запятой в таких местах, как шаблоны для сопоставления.
retaincomments
macro ? line&
  match instruction ; comment , line
    virtual
      comment
    end virtual
    instruction
  else
    line
  end match
end macro

var dd ?  ; bvar db ?
Директива "isolatelines" запрещает ассемблеру впоследствии объединять строки, считанные из исходного текста, когда разрыву строки предшествует обратный слеш.
Директива "removecomments" возвращает поведение точек с запятой по умолчанию, а директива "comblines" разрешает соединять строки исходного текста обычным образом.

No comments:

Post a Comment