Tuesday, April 9, 2019

flat assembler g. Введение и обзор


ENtoRU перевод flat assembler g. Introduction and Overview (update 08 Apr 2019)
(неоконченная версия) последняя правка 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