fasmg 介绍与概述
作者:Debuger 发布时间:July 13, 2026
flat assembler g
介绍与概述
译文说明:本文档为同目录 fasmg.txt 的中文译本;代码示例保持原文。
目录
什么是 flat assembler g?
它是如何工作的?
解析指令参数有哪些手段?
标号是如何处理的?
如何并行生成文件的多个段?
解析其他种类语法有哪些选择?
如何控制符号标识符的识别上下文?
如何定义与核心伪指令同名的指令?
如何将宏指令转换为 CALM?
什么是 flat assembler g?
它是一种汇编引擎,设计为 flat assembler 1 所用引擎的继任者;后者是 x86 处理器上公认的汇编器之一。
这是一个裸引擎:本身不能识别和编码任何处理器的指令,但有能力成为任意 CPU 架构的汇编器。
它拥有相比 flat assembler 1 大幅改进的宏指令语言,允许以可定制宏指令的形式轻松实现指令编码器。
本工具的源码可用 flat assembler 1 编译,也可用 flat assembler g 自身编译。
源码中包含根据所用汇编器包含不同头文件的子句。当 flat assembler g 编译自身时,使用提供的头文件集,
实现与 flat assembler 1 语法大体兼容的 x86 指令与格式。
本包中 x86 架构的示例程序,是原先随 flat assembler 1 提供的精选样例;
它们使用实现指令编码器与输出格式化器的头文件集,像原版 flat assembler 一样汇编这些程序。
为演示如何实现不同架构的指令集,包内还有微控制器 8051 与 AVR 的示例程序。
它们保持简单,因而不提供针对此类 CPU 编程的完整框架,但可为创建此类环境提供坚实基础。
另有组装 JVM 字节码的示例,是对原先为 flat assembler 1 所做样例的移植。
因此它有些粗糙,未充分利用新引擎的能力,但有助于可视化 class 文件的结构。
它是如何工作的?
flat assembler 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" 的地址值,而该地址又依赖
其前所有指令(含该跳转)的输出长度。这形成依赖环,汇编器需找到满足源码所创全部约束的解。
若汇编器只是命令式解释器,这将不可能。其语言因此在某些方面是声明式的。
为此类循环依赖求解难似解方程,甚至可构造 flat assembler g 确实能求解的例子:
x = (x-1)*(x+2)/2-2*(x+1)
db x
此处循环引用已归结为引用自身构造值的单一定义。flat assembler g 在此情况下能找到解,
但在许多其他情况下会失败。本汇编器所用方法是对源文多遍扫描,再凭所获知识预测所有值。
此法在机器码汇编中通常足够,但很少足以求解复杂方程;上例是例外之一。
解析指令参数有哪些手段?
并非所有指令都像前述例子那样语法简单。为处理可能含特殊构造的参数,
flat assembler 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)"。
不必逐个解析每种变体,可让汇编器求值表达式,并将所选符号作特殊处理。
符号声明为 "element" 时无固定值,在表达式中代数地当作多项式中的变量项。
element HL?
element IX?
element IY?
macro INC? argument
match [:r:], REG.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",该逻辑表达式为真,尽管参数畸形。
这是宏指令简单文本替换的后果(最好改用 CALM 避免)。此处可用局部变量作代理保存参数值:
macro INC? argument
match [:r:], REG.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" 声明可为符号关联附加值,再用 "metadata" 算符从含该变量为项的线性多项式中提取。
下例是演示此特性的 INC 宏另一变体:
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" 模式用于捕获含多个 token 的复杂表达式参数,防止 "INC A+0" 或 "INC A+B-A"。
对某些指令集是否加入此约束可因人而异。
"value eq value element 1" 确保值除寄存器名外不含其他项。即使强制参数仅含单个 token,
仍可能有复杂值,如 "X = A + B" 或 "Y = 2 * A";"INC X" 与 "INC Y" 会使 "element 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
此法还可处理参数含逗号或以不同方式分隔等更复杂情况。
CALM 宏能更精细控制参数解析,其 "match" 有更多选项。上宏可改写为:
calminstruction CALL? target&
local condition
match condition:name =, target, target, :
jno unconditional
check defined condition & condition relativeto CC
jno error
emit 1, 0C4h + (condition - CC) shl 3
jump address
error:
err "unrecognized syntax"
unconditional:
emit 1, 0CDh
address:
emit 2, target
end calminstruction
此次无需代理,因为参数文本仅在含它们的表达式求值时才被求值。
此外 "match" 可检查畸形表达式,就像检查条件符号名是否正确一样。
标号是如何处理的?
定义标号的标准方式是在名称后加 ":"(也起换行作用,同一行可跟任何其他命令,含另一标号)。
此类标号定义值等于当前地址的符号;初值为零,向输出添加字节时递增。
某些汇编语言变体希望标号可置于指令前而无需中间的 ":"。此时需创建标号宏,
在定义标号后将处理交给同名原宏指令:
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
用于定义标号的当前地址可用 "org" 改变。若需将标号与绝对值区分,可用 "element" 符号构成地址:
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
要在不写入输出的地址空间中定义标号,应声明 "virtual" 块。下例准备 "DATA" 与 "CODE" 宏,
在生成程序指令与数据标号之间切换;仅指令码进入输出:
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
此处用 "postpone" 确保 "virtual" 块总能正确关闭,即使源文以数据定义结束。
在上述环境中,任何指令都能区分数据标号与程序内标号。例如分支指令可接受程序内标号或绝对值,
但不接受数据标号:
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:
此上下文中 "CALL routine" 或 "CALL 1000h" 允许,"CALL variable" 不允许。
当标号值非绝对数时,可为使用它们的指令生成重定位。
可用特殊 "virtual" 块存储程序基址改变时需重定位的值在程序内的偏移:
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
如此创建的重定位表可用 "load" 访问。下列两行可将整表放入输出某处:
load RELOCATIONS : RELOCATION_COUNT shl 1 from Relocations : 0
dw RELOCATIONS
"load" 将整表读入单字符串,"dw" 写入输出(填充到字倍数;此例字符串无需填充)。
更复杂重定位可能需要额外修饰符。例如地址高低部分须存于不同位置并分别重定位时,
可实现如下修饰符:
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
为清晰省略了登记重定位的命令;此情况除代码内偏移外还需在相应结构中登记额外信息。
经此准备,代码中可生成:
BYTE HIGH address
BYTE LOW address
凡内部用 "byte" 宏生成代码的指令,都可借此轻松启用带修饰符的语法。
如何并行生成文件的多个段?
本引擎只有一个须顺序生成的主输出。当文件需含代码与数据等不同段,
且片段交错、散布于多个源文件时,这似乎成问题。但有若干方法处理,均或多或少依赖汇编器的前向引用能力。
自然做法是在 "virtual" 块中定义辅助段内容,再一次性复制到输出适当位置。
"virtual" 块带标号时可多次重开以追加数据。
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
即使无额外宏,语法也相对简单。
另一法是将段片段放入宏,在源文所需位置全部执行。缺点是追踪定义中的错误可能较麻烦。
便于并行段追加的技术也有利于生成重定位表等数据结构。
可在重开的 "virtual" 块内用常规数据伪指令代替早前演示用的 "store" 创建重定位记录。
解析其他种类语法有哪些选择?
有时汇编器需解析的命令并非以指令名或标号开头。名称前可能有 "."、"!" 等特殊字符,
或完全是不同构造。此时需用 "macro ?" 拦截整行源文并处理此类特殊语法。
例如若需允许 ".CODE" 命令,无法直接实现为宏,因为首点使符号被解释为局部符号,
无法这样执行全局定义的指令。拦截宏提供解法:
macro ? line&
match .=CODE?, line
CODE
else match .=DATA?, line
DATA
else
line
end match
end macro
含 ".CODE" 或 ".DATA" 的行在此被处理为调用对应名称的全局宏,其余拦截行不变执行。
此法可滤出特殊语法,让汇编器照常处理常规指令。
有时非传统语法仅出现在有边界的块内。解析宏应仅用于该处,块结束时用 "purge" 移除:
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
如此定义的宏不拦截控制汇编流的伪指令如 "if" 或 "repeat",块内仍可自由使用。
若声明为 "macro ?! line&",则会无例外拦截每一行。
另一选择是用 "struc ?" 仅拦截不以已知指令开头的行(首符号当作标号)。
因只测试未知命令,汇编开销应更小:
struc (head) ? tail&
match .=CODE?, head
CODE tail
else
head tail
end match
end struc
这些方法都有隐蔽陷阱:以 ":" 定义的标号后可在同一行跟另一指令。
若下一指令(此处藏在 "tail" 中)是 "if" 等控制伪指令,放在 "else" 分支会破坏控制块嵌套。
解法是在 "match" 块外 somehow 调用 "tail"。一法是通过特殊宏:
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
更简单的是直接调用原行;需覆盖时,用另一行拦截器使该行被忽略(随即自我清除):
struc (head) ? tail&
match .=CODE?, head
CODE tail
macro ? line&
purge ?
end macro
end match
head tail
end struc
更好的是用 CALM 代替标准宏:可在不用控制伪指令的情况下处理参数并汇编原行或修改行。
CALM 性能更好,对几乎每行都调用的拦截器尤为重要。
如何控制符号标识符的识别上下文?
设计通用宏时,须确保在任何调用处正确工作。"local" 可为宏的每次实例创建私有符号而不干扰未知环境,
但有时变量需在多次宏调用间共享。全局符号通常够用,但可能被干扰。考虑:
GLOBAL_STATE = 0
macro state
db GLOBAL_STATE
end macro
macro switch value
GLOBAL_STATE = GLOBAL_STATE xor (value)
end macro
此解法有问题:若别处也用 "GLOBAL_STATE" 会干扰这些宏。罕见特定全局名略有帮助
(多个全局最好用罕见名专用命名空间),但最好 somehow 隔离这些符号。
若在 "namespace" 块内使用上述宏,"GLOBAL_STATE" 的定义会在该命名空间创建符号。
第二问题有一基本解法:标识符末尾加点,确保定义的是与右侧访问相同的符号:
GLOBAL_STATE. = GLOBAL_STATE xor (value)
还有另一选择。宏的参数为其文本中标识符保留上下文(除非参数名前加 "&" 明确不要)。
下例演示效果:
macro tester arg
namespace X
a = 0
db a
db arg
end namespace
end macro
a = 3
tester a
两条汇编的数据定义文本同为 "db a",但第二条在宏调用处的上下文解释 "a",生成字节 3 而非 0。
这通过可视为文本着色的机制实现:参数 "arg" 的值有附加属性(如文本颜色),使第一个 "db a" 与第二个不同。
若在另一宏内定义宏,内层宏文本会保留着色。
下例用此机制改进早前 "state"/"switch" 宏:
macro setup variable
variable = 0
macro state
db variable
end macro
macro switch value
variable = variable xor (value)
end macro
end macro
setup GLOBAL_STATE
namespace Program
switch 8
GLOBAL_STATE = 0
state
end namespace
汇编 "setup GLOBAL_STATE" 时,"switch" 宏体含文本:
GLOBAL_STATE = GLOBAL_STATE xor (value)
其中两处 "GLOBAL_STATE" 均附着全局上下文。汇编 "switch 8" 时宏生成行:
GLOBAL_STATE = GLOBAL_STATE xor (8)
此时 "8" 带着调用 "switch" 处的上下文。
此变体 "switch" 与 "state" 无论何处调用都使用全局变量,类似许多语言中的闭包。
变量仍全局,其他代码仍可干扰。要完全隔离,可用 "local" 创建特殊参数:
同名文本但着色为宏实例唯一上下文:
macro setup
local variable
variable = 0
macro state
db variable
end macro
macro switch value
variable = variable xor (value)
end macro
end macro
setup
此类上下文传递也发生在符号变量的值上。用 "equ" 或 "define" 定义时,整个值标记为定义时的上下文。
用 "match" 或 "irpv" 将此类文本赋给参数时,各片段携带的上下文信息保留:
First:
.x = 1
define LIST .x
Second:
.x = 2
LIST equ LIST, .x
match values, LIST
display `values
db values
end match
"display" 显示从 "LIST" 用 "match" 提取的文本为 ".x, .x",但两标识符文本上下文不同,列表含两个不同值。
当 "match" 将标识符文本切成多段,各段保留原文本的上下文。
若标识符由不同来源的文本片段拼成,仅初始部分的上下文影响符号识别。
可在标识符前加 "#" 强制在当前上下文识别:
macro tester name
namespace my
name db ? ; symbol defined in its original namespace
#name db ? ; symbol defined in "my" namespace
end namespace
end macro
若仍不够,需剥离文本上一切上下文,"rawmatch" 伪指令正是如此。
类似效果可用 ` 将参数值转为字符串再用 "eval" 解释。
宏参数前加 "&" 不向参数值添加上下文,但也不移除已有上下文。可安全传递文本而不丢失信息,
也不污染 unwanted 上下文。多阶段示例:
calminstruction (var) transparent_equ &val&
publish :var, val
end calminstruction
namespace Windows
EOL := string 0x0A0D
link equ EOL
end namespace
EOL := 0x0A
match WinEOL, Windows.link
list equ WinEOL, EOL ; added context
list transparent_equ WinEOL, EOL ; no context added
end match
namespace C64
EOL := 0x0D
irpv items, list
db items
end irpv
end namespace
传给 "db" 的项文本均为 "EOL",但解释不同:Windows 命名空间上下文、全局上下文、
最后为无上下文原始文本(此时指向最近定义,即 C64 命名空间中的 EOL)。
如何定义与核心伪指令同名的指令?
语言大体可用宏实现,但需含与汇编器伪指令同名的命令。虽可用宏覆盖任何指令,
宏自身可能需要访问原伪指令。要使同名在不同上下文调用不同指令,可实现语言所在命名空间含覆盖宏,
需原伪指令的宏须临时切换到未覆盖的命名空间——每个此类宏须包在 "namespace" 块中。
还有一技巧,与宏参数或符号变量文本保留符号应被解释的上下文有关(含基命名空间与以点开头的父标号)。
宏文本通常不携带此类信息,但若宏构造方式使其含曾作为另一宏参数或符号变量一部分的文本,
则成为新宏一部分时仍保留上下文信息。例如:
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
给 "definitions" 宏的参数看似无用(每个 "end" 仍替换为 "end"),但来自参数的文本带上下文属性,
成为新宏一部分时保留。因此 "LABEL" 宏可在 "end" 指令已具不同含义的命名空间中使用,
宏体内的 "end" 仍指外层命名空间的符号。
本例参数大小写不敏感,会替换 "macro" 语句中本应定义在 "embedded" 命名空间的 "END"。
故用连接运算符拆开标识符以免被识别为参数。参数大小写敏感时则不必。
用符号变量代替宏参数,经 "match" 提取符号变量文本,可达同样效果:
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" 等控制伪指令定义的参数不会向文本添加上下文(除非已有)。
CALM 指令提供另一类解法。若定制指令集全以 CALM 定义,可能根本不需要原控制伪指令。
但若 CALM 需汇编可能不可达的伪指令,传给 "assemble" 的符号变量应为指令符号定义适当上下文。
如何将宏指令转换为 CALM?
经典宏指令由文本行组成,每次调用时预处理(用参数值替换参数名),再将预处理行送汇编。
例如下宏只生成一行待汇编行,将 "number" 替换为唯一参数给出的文本:
macro octet value*
db value
end macro
CALM 指令可视为定制预处理器,须用特殊语言编写。可用各种命令处理参数并生成待汇编行。
基本层面也可用 "arrange" 模拟标准预处理,之后须用 "assemble" 显式送汇编:
calminstruction octet value*
arrange value, =db value
assemble value
end calminstruction
结果与原宏相同,因进行同类预处理。但与宏文本不同,给 "arrange" 的模式须显式说明哪些名 token 替换为值、
哪些(以 "=" 为前缀)保持不动。从模式复制的 token 剥离一切上下文,如宏文本通常不携带
(而来自实参的值保留指令启动时的识别上下文)。
这是最直接转换法;"arrange" 与 "assemble" 序列可生成与原宏相同的行。但有一例外——宏执行 "local" 时,
创建指向该指令实例唯一命名空间中符号的预处理参数。
macro pointer
local next
dd next
next:
end macro
CALM 无此类命名空间;CALM 的局部命名空间在所有实例间共享。故每次调用需唯一符号时须手动构造,
例如向名称追加唯一数:
global_uid = 0
calminstruction pointer
compute global_uid, global_uid + 1
local command
arrange command, =dd =next#global_uid
assemble command
arrange command, =next#global_uid:
assemble command
end calminstruction
此处 "arrange" 的变量为数值,须替换为文本。仅当值为无非负项的纯非负数时可行,
此时 "arrange" 转为含该数十进制表示的文本 token。送汇编的行将含 "next#1" 等标识符。
全局计数器也可用 "arrange" 准备 "global_uid = global_uid + 1" 再送汇编递增;
"compute" 可在 CALM 处理器内直接完成。且不受改变汇编上下文的因素影响。
若指令为无条件且在跳过的 IF 块内使用,"compute" 仍会执行,因 CALM 命令执行——如标准预处理——
独立于主汇编流。对 "global_uid" 的引用始终指向 CALM 指令定义并编译时作用域中的同一符号。
用 "compute" 递增更可靠可预测。
类似地,定义标号的那行汇编可用 "publish" 代替。标号值(应等于含 "dd" 的行汇编后地址)须先计算,
因 "publish" 只执行符号赋值:
global_uid = 0
calminstruction pointer
compute global_uid, global_uid + 1
local symbol, command
arrange symbol, =next#global_uid
arrange command, =dd symbol
assemble command
local address
compute address, $
publish symbol:, address
end calminstruction
因 CALM 指令本身有条件,其内 "publish" effectively 也有条件,故正确替代带标号行的汇编。
全局计数器有诸多优点但可能被干扰,有时局部计数器更好。但 CALM 局部命名空间通常外部不可访问,
给此类计数器初值较难。可用 "take" 检查计数器是否已初始化:
calminstruction pointer
local id
take id, id
jyes increment
compute id, 0
increment:
compute id, id + 1
local symbol, command
arrange symbol, =next#id
arrange command, =dd symbol
assemble command
local address
compute address, $
publish symbol:, address
end calminstruction
但这增加每次调用都执行的命令。更好解法利用定义 CALM 时可定义定制语句:
calminstruction calminstruction?.init? var*, val:0
compute val, val
publish var, val
end calminstruction
calminstruction pointer
local id
init id, 0
compute id, id + 1
local symbol, command
arrange symbol, =next#id
arrange command, =dd symbol
assemble command
local address
compute address, $
publish symbol:, address
end calminstruction
定制语句 "init" 在定义 CALM 指令时调用(不向被定义指令生成可执行命令——若需生成须自行 "assemble")。
它接收 CALM 指令局部作用域中的变量名,用 "publish" 赋初始数值。
用符号值初始化局部变量,更简单的定制指令即可:
calminstruction calminstruction?.initsym? var*, val&
publish var, val
end calminstruction
"val" 参数文本携带含 "initsym" 语句的 CALM 定义处的识别上下文,故可为 "assemble" 准备含局部符号引用的文本:
calminstruction bigendian32? value
local command
initsym command, dd value
compute value, value bswap 4
assemble command
end calminstruction
编译后此指令仅含 "compute" 与 "assemble" 两条实际命令;局部符号 "command" 的值为文本,
在相同局部上下文解释,与 "compute" 引用同一 "value" 符号。
此例还演示 CALM 相对标准宏的另一优点:严格语义防止简单文本替换允许的多种不良行为。
"value" 文本由 "compute" 作为数值子表达式求值,意外语法会报错。故宜全程用 CALM 命令处理参数,
仅最终简单语句用 "assemble"。存在同等 CALM 命令时甚至可省去 "assemble":
calminstruction bigendian32? value
emit 4, value bswap 4
end calminstruction
操作完全独立于标准汇编过程;若含指令为无条件,即使在跳过的 "if" 块内或定义宏时调用也会生成输出
(且不向宏定义添加任何行)。
评论已关闭