flat assembler g. Введение и обзор
(неоконченная версия) последняя правка 30.04.2019
Введение в flat assembler g, которое объясняет назначение этого программного механизма и демонстрирует, как создать ассемблер для конкретной архитектуры ЦП с помощью макроинструкций.
Что такое flat assembler g?
Это программный механизм для сборки программ, разработанный как преемник используемого в flat assembler 1, одном из признанных ассемблеров для процессоров x86. Это голый "движок", который сам по себе не способен распознавать и кодировать инструкции какого-либо процессора, однако он способен стать ассемблером для архитектуры любого ЦП. Он имеет язык макроинструкций, который значительно улучшен по сравнению с языком, предоставляемым flat assembler 1, и позволяет легко реализовывать кодировщики инструкций в форме настраиваемых макроинструкций. Этот подход имеет большую гибкостью за счёт уменьшения производительности.Если бы случилось так, что требовалась бы очень быстрая сборка, сопоставимая с flat assembler 1, и недостаток макроинструкций в производительности был бы неприемлем, то можно было бы решить эту проблему путем создания пользовательского ассемблера на основе этого механизма, и полный исходный код был бы доступен для любой, кто хотел бы попробовать это. Но основное внимание в этом пакете уделяется использованию плоского ассемблера g в чистом виде.
Исходный код этого инструмента может быть скомпилирован с помощью плоского ассемблера 1, но также возможно использовать сам плоский ассемблер g для его компиляции. Источник содержит пункты, которые включают разные заголовочные файлы в зависимости от используемого ассемблера. Когда плоский ассемблер g компилирует себя, он использует макроинструкции, поставляемые с прилагаемыми примерами программ, поскольку они реализуют инструкции и форматы x86 с синтаксисом, совместимым с плоским ассемблером 1.
Макроинструкции, которые обрабатывают синтаксис инструкций x86, сложны и требуют много времени для сборки, но, между прочим, время, которое требуется плоскому ассемблеру g для компиляции на обычной современной машине, сопоставимо со временем, когда ранняя версия плоского ассемблера 1 Нужно было собрать полтора десятилетия назад на компьютере, который тогда был таким же посредственным. Это можно рассматривать как интересную демонстрацию того, как программное обеспечение может работать медленнее с той же скоростью, с какой аппаратное обеспечение становится быстрее.
Примеры программ для архитектуры x86, поставляемые в этом пакете, представляют собой выбранные образцы, изначально поставляемые с плоским ассемблером 1, с добавлением наборов макроинструкций, которые реализуют кодировщики команд и выходные форматтеры, необходимые для их сборки, как это делал оригинальный плоский ассемблер , Хотя они и не являются полными, они предназначены для поощрения создания дополнительных наборов макроинструкций, которые предоставили бы больше инструкций и форматов вывода.
Чтобы продемонстрировать, как могут быть реализованы наборы команд разных архитектур, есть несколько примеров программ для микроконтроллеров, 8051 и AVR. Они были простыми и, следовательно, они не обеспечивают полную основу для программирования таких процессоров, хотя они могут обеспечить прочную основу для создания таких сред.
Существует также пример сборки байт-кода JVM, который представляет собой преобразование образца, первоначально созданного для плоского ассемблера 1. По этой причине он несколько грубоват и не в полной мере использует возможности, предлагаемые новым механизмом. Однако это хорошо для визуализации структуры файла класса.
Как это работает?
Основная функция плоского ассемблера g - генерировать выходные данные, определенные инструкциями в исходном коде. Учитывая одну строку текста, как показано ниже, ассемблер сгенерирует один байт с указанным значением:
db 90h
Макроинструкции могут быть определены для генерации некоторых конкретных последовательностей данных в зависимости от предоставленных параметров. Они могут соответствовать инструкциям выбранного машинного языка, как в следующем примере, но они также могут быть определены для генерирования других видов данных для различных целей.macro int number
if number = 3
db 0CCh
else
db 0CDh, number
end if
end macro
int 20h ; generates two bytes
Сборка, рассматриваемая таким образом, может рассматриваться как некий интерпретируемый язык, и ассемблер, безусловно, обладает многими характеристиками интерпретатора. Однако он также разделяет некоторые аспекты с компилятором. Для инструкции возможно использовать значение, которое определено позже в источнике и может зависеть от инструкций, предшествующих этому определению, как показано в следующем примере.macro jmpi target
if target-($+2) < 80h & target-($+2) >= -80h
db 0EBh
db target-($+1)
else
db 0E9h
dw target-($+2)
end if
end macro
jmpi start
db 'some data'
start:
Определенный выше «jmpi» создает код инструкции перехода, как в архитектуре 8086. Такой код содержит относительное смещение цели перехода, хранящееся либо в однобайтовом, либо в 16-битном слове. Относительное смещение вычисляется как разница между адресом цели и адресом следующей инструкции. Специальный символ «$» предоставляет адрес текущей инструкции, и он используется для вычисления относительного смещения и определения, может ли оно поместиться в один байт.Поэтому код, сгенерированный «jmpi start» в приведенном выше примере, зависит от значения адреса, помеченного как «start», и это, в свою очередь, зависит от длины вывода всех предшествующих ему инструкций, включая упомянутый переход. Это создает цикл зависимостей, и ассемблеру необходимо найти решение, которое удовлетворяет всем ограничениям, созданным исходным текстом. Это было бы невозможно, если бы ассемблер был просто обязательным переводчиком. Таким образом, его язык в некоторых отношениях декларативен.
Поиск решения для таких круговых зависимостей может напоминать решение уравнения, и даже возможно построить пример, где плоский ассемблер g действительно способен решить одну:
x = (x-1)*(x+2)/2-2*(x+1)
db x
Циркулярная ссылка здесь сведена к единственному определению, которое ссылается на себя для построения значения. Плоский ассемблер g может найти решение в этом случае, хотя во многих других он может потерпеть неудачу. Метод, используемый этим ассемблером, состоит в том, чтобы выполнить несколько проходов по исходному тексту, а затем попытаться предсказать все значения с помощью знаний, собранных таким образом. Этот подход в большинстве случаев достаточно хорош для сборки машинных кодов, но редко бывает достаточно для решения сложных уравнений, и приведенный выше пример является одним из исключений.Каковы средства анализа аргументов инструкции?
Не все инструкции имеют простой синтаксис, как в предыдущих примерах. Чтобы помочь в обработке аргументов, которые могут содержать специальные конструкции, плоский ассемблер g предоставляет несколько способных инструментов, продемонстрированных ниже на примерах, которые реализуют несколько отдельных инструкций процессора Z80. Правила использования представленных функций приведены в руководстве.
Когда инструкция имеет очень небольшой набор разрешенных аргументов, каждый из них может обрабатываться отдельно с помощью конструкции «match»:
macro EX? first,second
match (=SP?), first
match =HL?, second
db 0E3h
else match =IX?, second
db 0DDh,0E3h
else match =IY?, second
db 0FDh,0E3h
else
err "incorrect second argument"
end match
else match =AF?, first
match =AF'?, second
db 08h
else
err "incorrect second argument"
end match
else match =DE?, first
match =HL?, second
db 0EBh
else
err "incorrect second argument"
end match
else
err "incorrect first argument"
end match
end macro
EX (SP),HL
EX (SP),IX
EX AF,AF'
EX DE,HL
"?" символ появляется во многих местах, чтобы пометить имена как нечувствительные к регистру, и все эти вхождения могут быть удалены, чтобы еще больше упростить пример.Когда набор возможных значений аргумента больше, но имеет некоторые закономерности, можно определить текстовые замены, чтобы заменить некоторые символы тщательно подобранными конструкциями, которые затем могут быть распознаны и проанализированы:
A? equ [:111b:]
B? equ [:000b:]
C? equ [:001b:]
D? equ [:010b:]
E? equ [:011b:]
H? equ [:100b:]
L? equ [:101b:]
macro INC? argument
match [:r:], argument
db 100b + r shl 3
else match (=HL?), argument
db 34h
else match (=IX?+d), argument
db 0DDh,34h,d
else match (=IY?+d), argument
db 0FDh,34h,d
else
err "incorrect argument"
end match
end macro
INC A
INC B
INC (HL)
INC (IX+2)
Этот подход имеет черту, которая не всегда может быть желательной: он позволяет использовать выражение типа «[: 0:]» непосредственно в аргументе. Но можно предотвратить использование синтаксиса таким образом, используя префикс в конструкции «match»:REG.A? equ [:111b:]
REG.B? equ [:000b:]
REG.C? equ [:001b:]
REG.D? equ [:010b:]
REG.E? equ [:011b:]
REG.H? equ [:100b:]
REG.L? equ [:101b:]
macro INC? argument
match [:r:], REG.argument
db 100b + r shl 3
else match (=HL?), argument
db 34h
else match (=IX?+d), argument
db 0DDh,34h,d
else match (=IY?+d), argument
db 0FDh,34h,d
else
err "incorrect argument"
end match
end macro
В случае аргумента, структурированного как «(IX + d)», иногда может потребоваться разрешить другие алгебраически эквивалентные формы выражения, например «(d + IX)» или «(c + IX + d)». Вместо того, чтобы анализировать каждый возможный вариант по отдельности, можно позволить ассемблеру оценивать выражение, одновременно обрабатывая выбранный символ. Когда символ объявляется как «элемент», он не имеет значения, а когда он используется в выражении, он рассматривается алгебраически как переменный член в полиноме.элемент HL?
element HL?
element IX?
element IY?
macro INC? argument
match [:r:], argument
db 100b + r shl 3
else match (a), argument
if a eq HL
db 34h
else if a relativeto IX
db 0DDh,34h,a-IX
else if a relativeto IY
db 0FDh,34h,a-IY
else
err "incorrect argument"
end if
else
err "incorrect argument"
end match
end macro
INC (3*8+IX+1)
virtual at IX
x db ?
y db ?
end virtual
INC (y)
Существует небольшая проблема с вышеуказанной макроинструкцией. Параметр может содержать любой текст, и когда такое значение помещается в выражение, оно может вызвать ошибочное поведение. Например, если обработать «INC (1 | 0)», выражение «a eq HL» будет преобразовано в «1 | 0 eq HL», и это логическое выражение будет правильным и истинным, даже если аргумент был искажен. Чтобы этого не происходило, в качестве прокси-сервера может использоваться локальная переменная, содержащая значение аргумента:macro INC? argument
match [:r:], argument
db 100b + r shl 3
else match (a), argument
local value
value = a
if value eq HL
db 34h
else if value relativeto IX
db 0DDh,34h,a-IX
else if value relativeto IY
db 0FDh,34h,a-IY
else
err "incorrect argument"
end if
else
err "incorrect argument"
end match
end macro
У такой прокси-переменной есть дополнительное преимущество благодаря тому, что ее значение вычисляется до того, как макроинструкция начнет генерировать какой-либо вывод. Когда выражение содержит символ, такой как «$», оно может давать разные значения в зависимости от того, где оно вычисляется, и использование прокси-переменной гарантирует, что полученное значение будет тем, которое получено путем оценки аргумента перед генерацией кода инструкции.Когда набор символов, разрешенных в выражениях, больше, лучше иметь единую конструкцию для обработки всего их семейства. Объявление «элемента» может ассоциировать дополнительное значение с символом, и эта информация затем может быть получена с помощью оператора «метаданных», примененного к линейному полиному, который содержит данный символ в качестве переменной. Следующий пример - еще один вариант предыдущей макроинструкции, демонстрирующий использование этой функции:
element register
element A? : register + 111b
element B? : register + 000b
element C? : register + 001b
element D? : register + 010b
element E? : register + 011b
element H? : register + 100b
element L? : register + 101b
element HL?
element IX?
element IY?
macro INC? argument
local value
match (a), argument
value = a
if value eq HL
db 34h
else if value relativeto IX
db 0DDh,34h,a-IX
else if value relativeto IY
db 0FDh,34h,a-IY
else
err "incorrect argument"
end if
else match any more, argument
err "incorrect argument"
else
value = argument
if value eq value element 1 & value metadata 1 relativeto register
db 100b + (value metadata 1 - register) shl 3
else
err "incorrect argument"
end if
end match
end macro
Паттерн «any more» предназначен для перехвата любого аргумента, который содержит сложные выражения, состоящие из более чем одного токена. Это предотвращает использование синтаксиса, такого как «INC A + 0» или «INC A + B-A». Но в случае некоторых наборов инструкций включение такого ограничения может зависеть от личных предпочтений.Условие «value eq value element 1» гарантирует, что значение не содержит никаких терминов, кроме имени регистра. Даже если аргумент вынужден содержать не более одного токена, все же возможно, что он имеет комплексное значение, например, если были такие определения, как «X = A + B» или «Y = 2 * A». И «INC X», и «INC Y» затем заставят оператор «элемент 1» возвращать значение «A», которое отличается от значения, проверенного в любом случае.
Если инструкция принимает переменное число аргументов, простой способ распознать ее различные формы - объявить аргумент с модификатором «&» для передачи полного содержимого аргументов в «match»:
element CC
NZ? := CC + 000b
Z? := CC + 001b
NC? := CC + 010b
C? := CC + 011b
PO := CC + 100b
PE := CC + 101b
P := CC + 110b
M := CC + 111b
macro CALL? arguments&
local cc,nn
match condition =, target, arguments
cc = condition - CC
nn = target
db 0C4h + cc shl 3
else
nn = arguments
db 0CDh
end match
dw nn
end macro
CALL 0
CALL NC,2135h
Этот подход также позволяет обрабатывать другие, более сложные случаи, например, когда аргументы могут содержать запятые или разделяться различными способами.Как обрабатываются этикетки?
Стандартный способ определения метки состоит в том, что после ее имени следует «:» (это также действует как разрыв строки, и любая другая команда, включая другую метку, может следовать в той же строке). Такая метка просто определяет символ со значением, равным текущему адресу, который изначально равен нулю и увеличивается, когда в вывод добавляются какие-либо байты.
В некоторых вариантах ассемблера может быть желательно разрешить метке предшествовать инструкции без дополнительного символа ":" между ними. Затем необходимо создать помеченную макроинструкцию, которая после определения метки передает обработку исходной макроинструкции с тем же именем:
struc INC? argument
.:
INC argument
end struc
start INC A
INC B
iterate instruction, EX,INC,CALL
struc instruction? argument
.: instruction argument
end struc
end iterate
Это должно быть сделано для каждой инструкции, которая должна разрешить такой синтаксис. Достаточно простого цикла, подобного следующему:struc ? tail&
match :, tail
.:
else match : instruction, tail
.: instruction
else match == value, tail
. = value
else
.: tail
end match
end struc
Каждая встроенная инструкция, которая определяет данные, уже имеет помеченный вариант.Определяя помеченную инструкцию, которая имеет "?" вместо имени можно перехватить каждую строку, которая начинается с идентификатора, который не является известной инструкцией и поэтому считается меткой. Следующий позволяет метке без «:» начинать любую строку в исходном тексте (он также обрабатывает особые случаи, чтобы после меток следовали «:» или «=» и значение все равно работало):
struc ? tail&
match :, tail
label . at $ shr 1
else match : instruction, tail
label . at $ shr 1
instruction
else
. tail
end match
end struc
Очевидно, что больше не требуется определять какие-либо конкретные помеченные макроинструкции, когда применяется глобальный эффект такого рода. Вариант должен быть выбран в зависимости от типа синтаксиса, который должен быть разрешен.Перехват даже меток, определенных с помощью «:», может стать полезным, когда значение текущего адреса требует некоторой дополнительной обработки перед назначением метке - например, когда процессор использует адреса с единицей больше, чем байт. Перехватывающая макроинструкция может выглядеть так:
element CODEBASE
org CODEBASE + 0
macro CALL? argument
local value
value = argument
if value relativeto CODEBASE
db 0CDh
dw value - CODEBASE
else
err "incorrect argument"
end if
end macro
Значение текущего адреса, которое используется для определения меток, может быть изменено с помощью «org». Если метки необходимо отличать от абсолютных значений, символ, определенный с помощью «элемента», может использоваться для формирования адреса:element DATA
DATA_OFFSET = 2000h
element CODE
CODE_OFFSET = 1000h
macro DATA?
_END
virtual at DATA + DATA_OFFSET
end macro
macro CODE?
_END
org CODE + CODE_OFFSET
end macro
macro _END?
if $ relativeto DATA
DATA_OFFSET = $ - DATA
end virtual
else if $ relativeto CODE
CODE_OFFSET = $ - CODE
end if
end macro
postpone
_END
end postpone
CODE
Чтобы определить метки в адресном пространстве, которое не будет отражено в выводе, должен быть объявлен «виртуальный» блок. Следующий пример подготавливает макроинструкции «ДАННЫЕ» и «КОД» для переключения между генерацией программных инструкций и меток данных. Только коды команд будут отправлены на выход:macro CALL? argument
local value
value = argument
if value relativeto CODE
db 0CDh
dw value - CODE
else if value relativeto 0
db 0CDh
dw value
else
err "incorrect argument"
end if
end macro
DATA
variable db ?
CODE
routine:
Блок «отложить» используется здесь, чтобы гарантировать, что «виртуальный» блок всегда закрывается правильно, даже если исходный текст заканчивается определениями данных.В среде, подготовленной вышеупомянутым образцом, любая инструкция сможет отличить метки данных от меток, определенных в программе. Например, можно выполнить инструкцию ветвления, чтобы принять аргумент, являющийся либо меткой в программе, либо абсолютным значением, но запретить любую метку данных:
virtual at 0
Relocations::
rw RELOCATION_COUNT
end virtual
RELOCATION_INDEX = 0
postpone
RELOCATION_COUNT := RELOCATION_INDEX
end postpone
macro WORD? value
if value relativeto CODE
store $ - CODE : 2 at Relocations : RELOCATION_INDEX shl 1
RELOCATION_INDEX = RELOCATION_INDEX + 1
dw value - CODE
else
dw value
end if
end macro
macro CALL? argument
local value
value = argument
if value relativeto CODE | value relativeto 0
db 0CDh
word value
else
err "incorrect argument"
end if
end macro
В этом контексте «CALL рутина» или «CALL 1000h» будет разрешено, а «CALL variable» не будет.Когда метки имеют значения, которые не являются абсолютными числами, можно генерировать перемещения для инструкций, которые их используют. Специальный «виртуальный» блок может использоваться для хранения смещений значений внутри программы, которые необходимо переместить при изменении ее базы:
load RELOCATIONS : RELOCATION_COUNT shl 1 from Relocations : 0
dw RELOCATIONS
Таблица перемещений, созданная таким образом, может быть доступна с помощью «load». Следующие две строки могут быть использованы для размещения таблицы целиком где-то в выводе:element MOD.HIGH
element MOD.LOW
HIGH? equ MOD.HIGH +
LOW? equ MOD.LOW +
macro BYTE? value
if value relativeto MOD.HIGH + CODE
; register HIGH relocation
db (value - MOD.HIGH - CODE) shr 8
else if value relativeto MOD.LOW + CODE
; register LOW relocation
db (value - MOD.LOW - CODE) and 0FFh
else if value relativeto MOD.HIGH
db (value - MOD.HIGH) shr 8
else if value relativeto MOD.LOW
db (value - MOD.LOW) and 0FFh
else
db value
end if
end macro
«Load» считывает всю таблицу в одну строку, а затем «dw» записывает ее в вывод (дополняемый несколькими словами, но в этом случае строка никогда не требует такого дополнения).Для более сложных типов перемещений может потребоваться дополнительный модификатор. Например, если верхнюю и нижнюю части адреса необходимо хранить в отдельных местах (например, в двух инструкциях) и перемещать отдельно, необходимые модификаторы могут быть реализованы следующим образом:
BYTE HIGH address
BYTE LOW address
Команды, которые регистрировали бы перемещение, были опущены для ясности, в этом случае не только смещение в коде, но и некоторая дополнительная информация должна была бы быть зарегистрирована в соответствующих структурах. С такой подготовкой перемещаемые блоки в коде могут быть сгенерированы как:include '8086.inc'
org 100h
jmp CodeSection
DataSection:
virtual
Data::
end virtual
postpone
virtual Data
load Data.OctetString : $ - $$ from $$
end virtual
end postpone
db Data.OctetString
CodeSection:
virtual Data
Hello db "Hello!",24h
end virtual
mov ah,9
mov dx,Hello
int 21h
virtual Data
ExitCode db 37h
end virtual
mov ah,4Ch
mov al,[ExitCode]
int 21h
Такой подход позволяет легко включить синтаксис с модификаторами в любой инструкции, которая внутренне использует «байтовую» макроинструкцию при генерации кода.Как можно создать несколько разделов файла параллельно?
Этот механизм сборки имеет единственный основной выход, который должен генерироваться последовательно. Это может показаться проблематичным, когда файл должен содержать отдельные разделы для кода и данных, но содержимое этих разделов должно быть собрано из чередующихся фрагментов, которые могут быть распределены по нескольким исходным файлам. Тем не менее, есть несколько относительно простых методов, которые позволяют это сделать, и все они так или иначе основаны на возможностях прямой ассемблерной ссылки.
Естественный подход заключается в определении содержимого вспомогательного раздела в «виртуальном» блоке и копировании его в соответствующую позицию в выводе с помощью одной операции. Когда «виртуальный» блок помечен, его можно повторно открыть несколько раз, чтобы добавить к нему больше данных.
macro ? line&
match .=CODE?, line
CODE
else match .=DATA?, line
DATA
else
line
end match
end macro
Это приводит к относительно простому синтаксису даже без помощи каких-либо дополнительных макросов.Другой метод может заключаться в том, чтобы поместить фрагменты раздела в макросы и выполнить их все в нужной позиции в источнике. Недостатком такого подхода является то, что отслеживание ошибок в определениях может стать немного громоздким.
Методы, которые позволяют легко добавлять к разделу, сгенерированному параллельно, также могут быть очень полезны для создания структур данных, таких как таблицы перемещения. Вместо команд «store», использовавшихся ранее при демонстрации концепции, можно использовать обычные директивы данных внутри повторно открытого «виртуального» блока для создания записей перемещения.
Какие есть варианты для синтаксического анализа других типов синтаксиса?
В некоторых случаях команда, которую ассемблер должен проанализировать, может начинаться с чего-то отличного от имени инструкции или метки. Может случиться так, что имени предшествует специальный символ, такой как «.» или "!", или что это совершенно другой вид конструкции. Тогда нужно использовать «макрос?» перехватывать целые строки исходного текста и обрабатывать любой специальный синтаксис такого рода.
Например, если бы требовалось разрешить команду, написанную как «.CODE», было бы невозможно реализовать ее непосредственно как макроинструкцию, потому что начальная точка вызывает интерпретацию символа как локальной, а глобально определенная инструкция никогда не сможет быть исполненным таким образом. Перехватывающая макроинструкция обеспечивает решение:
macro concise
macro ? line&
match =end =concise, line
purge ?
else match dest+==src, line
ADD dest,src
else match dest-==src, line
SUB dest,src
else match dest==src, line
LD dest,src
else match dest++, line
INC dest
else match dest--, line
DEC dest
else match any, line
err "syntax error"
end match
end macro
end macro
concise
C=0
B++
A+=2
end concise
Строки, содержащие текст «.CODE» или «.DATA», обрабатываются здесь таким образом, что они вызывают глобальную макроинструкцию с соответствующим именем, тогда как все остальные перехваченные строки выполняются без изменений. Этот метод позволяет отфильтровать любой специальный синтаксис и позволить ассемблеру обрабатывать обычные инструкции как обычно.Иногда нетрадиционный синтаксис ожидается только в определенной области исходного текста, например, внутри блока с определенными границами. Макроинструкция синтаксического анализа должна затем применяться только в этом месте и удаляться с помощью «продувки», когда заканчивается блок:
struc (head) ? tail&
match .=CODE?, head
CODE tail
else
head tail
end match
end struc
Макроинструкция, определенная таким образом, не перехватывает строки, которые содержат директивы, управляющие потоком сборки, такие как «if» или «repeat», и они все еще могут свободно использоваться внутри такого блока. Это изменится, если объявление будет в форме "macro?! Line &". Такой вариант будет перехватывать каждую строку без исключения.Другим вариантом для отлова специальных команд может быть использование "struc?" перехватывать только те строки, которые не начинаются с известной инструкции (исходный символ затем обрабатывается как метка). Так как этот тест проверяет только неизвестные команды, он должен вызывать меньше затрат на сборку:
struc (head) ? tail&
local invoker
match .=CODE?, head
macro invoker
CODE tail
end macro
else
macro invoker
head tail
end macro
end match
invoker
end struc
Все эти подходы скрывают тонкую ловушку. За меткой, определенной с помощью «:», может следовать другая инструкция в той же строке. Если эта следующая инструкция (которая здесь становится скрытой в параметре tail) является директивой управления, такой как «if», то помещение ее в предложение «else» приведет к нарушению вложенности блоков управления. Решение состоит в том, чтобы каким-то образом вызывать содержимое «tail» вне блока «match». Одним из способов может быть вызов специального макроса:struc (head) ? tail&
match .=CODE?, head
CODE tail
macro ? line&
purge ?
end macro
end match
head tail
end struc
Но более простой вариант - вызвать исходную строку напрямую и, когда необходимо переопределение, заставить ее игнорироваться с помощью другого перехватчика строки (избавиться от него сразу после):macro definitions end?
namespace embedded
struc LABEL? size
match , size
.:
else
label . : size
end match
end struc
macro E#ND? name
end namespace
match any, name
ENTRYPOINT := name
end match
macro ?! line&
end macro
end macro
end macro
definitions end
start LABEL
END start
Можно добавить еще один макрос с целью игнорирования следующей строки, чтобы сделать это решение более компактным.Может случиться так, что язык в общем случае может быть легко реализован с помощью макросов, но он должен включать команду с тем же именем, что и одна из директив ассемблера. Хотя можно переопределить любую инструкцию макросом, для самих макросов может потребоваться доступ к исходной директиве. Чтобы одно и то же имя вызывало другую инструкцию в зависимости от контекста, реализованный язык может интерпретироваться в пространстве имен, которое содержит переопределяющий макрос, в то время как все макросы, требующие доступа к исходной директиве, должны были бы временно переключиться на другое пространство имен, где оно не имеет был отменен Это потребует от каждого такого макроса упаковки его содержимого в блок «пространства имен».
Но есть еще одна хитрость, связанная с тем, как тексты макропараметров или символических переменных сохраняют контекст, в котором следует интерпретировать символы внутри них (это включает в себя базовое пространство имен и родительскую метку для символов, начинающихся с точки).
В отличие от двух упомянутых случаев, текст макроса обычно не несет такой дополнительной информации, но если макрос сконструирован таким образом, что он содержит текст, который когда-то переносился в параметре в другой макрос или в символьную переменную, тогда это текст сохраняет информацию о контексте, даже когда он становится частью вновь определенного макроса. Например:
Параметр, данный макросу «определения», может показаться, что он ничего не делает, так как он заменяет каждый экземпляр «конца» одним и тем же словом - но текст, который приходит из параметра, снабжен дополнительной информацией о контексте, и этот атрибут затем сохраняется, когда текст становится частью нового макроса. Благодаря этому макрос «LABEL» может использоваться в пространстве имен, где инструкция «end» приняла другое значение, но экземпляры «end» в его теле все еще ссылаются на символ во внешнем пространстве имен.В этом примере параметр стал нечувствительным к регистру, и, таким образом, он заменит даже оператор «END» в «макросе», который должен определять символ во «встроенном» пространстве имен. По этой причине идентификатор был разделен с помощью оператора конкатенации, чтобы предотвратить его распознавание в качестве параметра. В этом нет необходимости, если параметр учитывает регистр (как обычно).
Тот же эффект может быть достигнут за счет использования символических переменных вместо макропараметров с помощью «соответствия» для извлечения текста символической переменной:
define link end
match end, link
namespace embedded
struc LABEL? size
match , size
.:
else
label . : size
end match
end struc
macro END? name
end namespace
match any, name
ENTRYPOINT := name
end match
macro ?! line&
end macro
end macro
end match
start LABEL
END start
Это не будет работать без передачи текста через символьную переменную, поскольку параметры, определенные управляющими директивами, такими как «match», не добавляют контекстную информацию к тексту, если он уже не был там.Инструкции, которые должны быть доступны на пользовательском языке, должны были бы быть определены с помощью одного из этих приемов, если внутри они использовали какие-либо команды, которые обычно недоступны.
No comments:
Post a Comment