小工具搬运工

搬运一些实用的小工具

flat assembler g
用户手册

译文说明:本文档为同目录 manual.txt 的中文译本;代码示例、指令名与注释中的汇编代码保持原文。

本文件描述 flat assembler g 语言的语法,并附基本示例。编写时假定读者顺序阅读,
且任一时点仅使用前文已介绍的概念与构造。也可直接跳到感兴趣的章节,需要时再回看前文。

目录

  1. 运行汇编器
  2. 基本语法规则
  3. 符号标识符
  4. 基本符号定义
  5. 表达式值
  6. 符号类
  7. 生成数据
  8. 条件汇编
  9. 宏指令
  10. 标号宏指令
  11. 符号变量与识别上下文
  12. 重复指令块
  13. 匹配参数
  14. 输出区域
  15. 源与输出控制
  16. CALM 指令
  17. CALM 中的汇编命令
  18. CALM 中的高级匹配
  1. 运行汇编器

从命令行启动汇编须至少提供一个参数:源文件名;可选第二个参数为目标文件名。
汇编成功则将生成输出写入目标文件并显示简短摘要,否则显示错误信息。
可用附加开关 "-e" 控制最多显示的错误数(默认不超过一个)。
"-p" 控制汇编器尝试的最大遍数(默认 100)。
"-r" 设置递归栈限制,即进入宏指令与包含额外源文件的最大深度。
"-v" 可在报错时显示该栈的全部行(默认汇编器尝试只选最有信息量的行,此简单启发式未必总正确)。
"-v" 取值为 2 时,还会在每一遍实时显示源文中命令产生的全部消息。
"-i" 可在被处理源文开头插入任意命令。

  1. 基本语法规则

汇编语言中每条命令占一行文本。
若行含分号,从该字符到行尾视为注释,汇编器忽略。
行主体(不含注释)可以反斜杠结尾,此时源文下一行将拼接于此行之后。
这可按需将命令拆成多行。下文所称源行指去掉注释并将反斜杠连接行合并后的实体。
源行文本划分为称为 token 的语法单元。若干特殊字符单独成 token,下列字符均属此类:

+-/*=<>()[]{}:?!,.|&~#`\

不被空白打断的、非上述字符的连续序列成为一个 token,可为名称或数字。
例外:序列以单引号或双引号开头时定义引号字符串,可含特殊字符、空白甚至分号,
仅以与起始相同的引号结束。包围字符串的引号本身不进入字符串。
若字符串内需含与包围相同的引号,须在字符串内将该字符加倍——只保留一个副本,序列继续。
数字与名称的区分:以十进制数字开头,或以 "$" 后跟任意十六进制数字开头,则视为数字 token
(即使并非合法数字)。合法数字须为下列之一:十进制(可选后缀 "d")、后缀 "b" 的二进制、
后缀 "o" 或 "q" 的八进制、前缀 "$" 或 "0x" 或后缀 "h" 的十六进制。
十六进制首位可为字母时,可能须前加数字零以识别为数字,例如 "0Ah" 合法而 "Ah" 仅为名称。
除数字外,数字中可用下划线或单引号作分隔或填充,便于阅读较长数字。

  1. 符号标识符

任何名称只要被赋予含义(值)即可成为已定义符号。
用 "=" 命令是创建具给定值符号的最简单方法之一:

    a = 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

每个符号有子命名空间(child namespace)。两名称以点连接(中间无空白)时,
标识符指第一符号之子命名空间中由第二名称命名的实体;可链式重复,指任意长度后代链。
链中任一段后可加 "?"。若 "?" 插在名称中间( effectively 拆成多 token)则为语法错误。
标识符以点开头(父符号名为空)时,指当前行之前最近定义的普通标号之子命名空间中的符号。

    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",会改写原 "regional" 而非在子空间新建。
如此形成的定义也可不论先前是否大小写不敏感而赋新值。
标识符仅为单个点时,指最近不以点开头的标号。可再改写前述示例:

    space:
    namespace .
            x = 1
            y = 2
            color:
            namespace .
                    r = 0
                    g = 0
                    b = 0
            end namespace
    end namespace

亦演示 namespace 块可嵌套。
"#" 可插在标识符内任意位置而不改变含义。仅 "#" 分隔两名称 token 时,合并为连接名。

    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

此类修饰标识符在求值(非定义)处仍指与定义处相同的符号。故除非标识符还用点,总指当前命名空间中的符号。
数字可作标识符内名称段,但不能放在首位(否则为字面量)。可在标识符前加 "?" 绕过:

    in.1 = 3
    namespace in
        ?2 = ?1 + 1             ; in.2 = 4
    end namespace
  1. 基本符号定义

标号定义的符号在全源中只能定义一次。如此赋予的值可从源中任何地方访问,甚至在标号实际定义之前
(前向引用)。汇编器多遍扫描源文并预测符号值;仅当所有预测正确才生成最终输出。
只能定义一次、因而恒具可前向引用统一值的符号称为常量。所有标号都是常量。
用 "=" 定义的符号可有多次此类定义,称为变量;使用时取最新定义的值。
用 "=" 定义的符号也可前向引用,但仅当全源中恰好定义一次、从而有单一明确值时。

    a = 1           ; a = 1
    a = a + 1       ; a = 2
    a = b + 1       ; a = 3
    b = 2

前向引用的特例是自引用:符号值用于自身定义。仅当汇编器能找到在此求值下稳定的值( effectively 解方程)时汇编成功;
但基于预测的解析算法简单,即使解存在也可能找不到。

    x = (x-1)*(x+2)/2-2*(x+1)       ; x = 6 or x = -1

":=" 定义常量值,可代替 "=" 以确保符号只定义一次且可前向引用。
"=:" 像 "=" 一样定义变量,但对先前值(若存在)的处理不同:"=" 丢弃旧值,"=:" 保留以便 "restore" 恢复:

    a = 1
    a =: 2          ; preserves a = 1
    a = 3           ; discards a = 2 and replaces it with a = 3
    restore a       ; brings back a = 1

"restore" 后可跟多个以逗号分隔的符号标识符,丢弃各自最近一次定义。
对从未定义或已全部丢弃的符号使用 "restore" 不报错。
经 "restore" 处理的符号变为变量,永不可前向引用;故 "restore" 不能用于常量。
"label" 关键字后跟符号标识符是定义标号的另一方式。基本形式等价于 ":",但独占一行。
可用 ":" 及附加值(通常表示被标实体大小);可用内建大小常量或 plain 数字。

    label character:byte
    label char:1

":" 可用空白代替,但建议保留以利清晰。标识符与可选大小之后可有 "at" 及应赋给标号的值(而非当前地址)。

    label wchar:word at char

内建大小常量等价于:

    byte? = 1       ; 8 bits
    word? = 2       ; 16 bits
    dword? = 4      ; 32 bits
    fword? = 6      ; 48 bits
    pword? = 6      ; 48 bits
    qword? = 8      ; 64 bits
    tbyte? = 10     ; 80 bits
    tword? = 10     ; 80 bits
    dqword? = 16    ; 128 bits
    xword? = 16     ; 128 bits
    qqword? = 32    ; 256 bits
    yword? = 32     ; 256 bits
    dqqword? = 64   ; 512 bits
    zword? = 64     ; 512 bits

"element" 后跟符号标识符定义无固定值的特殊常量,可在线性多项式中作变量使用。
标识符后可跟 ":" 及与此符号关联的值,称为元素的 metadata。

    element A
    element B:1

赋给符号的 metadata 可用下一节定义的特殊算符提取。

  1. 表达式值

迄今凡需提供值之处(如 "=" 之后或 "at" 之后),可为字面量(数字或引号字符串)或符号标识符。
也可通过含内建算符的表达式指定值。
"+"、"-"、"" 对整数作标准算术("+"、"-" 可一元)。"/" 与 "mod" 作带余除法,分别得商与余数。
算术算符中 "mod" 优先级最高,"
" "/" 次之,"+"、"-" 最后(含一元)。同级从左到右。可用括号改变顺序。
"xor"、"and"、"or" 对数字作按位运算:"xor" 为异或,"and" 为按位与,"or" 为按位或。优先级高于一切算术算符。
"shl"、"shr" 将第一参数按第二参数指定位数移位;"shl" 左移,"shr" 右移向零并丢弃落入小数部分的位。优先级高于其他二元位运算。
"not"、"bsf"、"bsr" 为一元算符,优先级更高:"not" 按位取反;"bsf"/"bsr" 找最低/最高置位并返回索引。
数字运算仿佛在无界 2-adic 表示上进行。例如 "bsr" 对负数无有效结果(无穷个 1 延伸),报错。
"bswap" 按反字节序(大端)生成数字的字节串表示;第二参数为所需字节长度。与 "shl"/"shr" 同级。
字符串作数字运算参数时视为位序列并自动转为正整数(向无穷延伸零位);连续字符对应越来越高位的位。
数字转字符串可用一元 "string"(最低优先级,其后的整个表达式先求值)。反向只需一元 "+"。
字符串长度用一元 "lengthof"(最高优先级之一)。
"bappend" 将第二参数字符串的字节序列追加到第一参数;任一为数字则隐式转字符串。与二元位运算同级。
"element" 符号用于表达式时结果可为以该符号为变量的线性多项式。多项式项仅允许保持线性的运算(多项式×数字可以,×另一多项式不行)。
若干高优先级算符从线性多项式项提取信息:第一参数为多项式,第二参数为项索引。
"element" 提取项的变量(系数为一),"scale" 提取系数,"metadata" 返回变量关联的 metadata。
第二参数索引超过最后一项时三算符均返回 0。索引为 0 时 "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" 索引 0 时返回与第一参数关联的 size;仅当第一参数为有关联 size 的符号(或含此类符号的算术表达式)时确定,否则为 0。
一元 "sizeof" 与 "metadata 0" 相同。
"elementof"、"scaleof"、"metadataof" 为参数顺序相反的变体;表达式中 "sizeof" 等价于 "0 metadataof"。
比对应算符优先级更高且右结合。
线性多项式项顺序取决于值的构造方式:算术运算保留第一操作数项序,第一操作数没有的项按第二操作数中出现顺序接在末尾。仅在用算符提取项时重要。
"elementsof" 为一元最高优先级算符,计线性多项式变量项个数。
表达式可含浮点字面量:须十进制;可有 ".";可有 "e" 及十进制指数(可选 "+" "-");含 "." 或 "e" 时其后至少一位数字;末尾可加 "f"。
无 "." 与 "e" 时,仅末尾 "f" 可强制为浮点而非十进制整数。
浮点以二进制形式处理;范围与精度至少不低于汇编器能在输出中产生的最长浮点格式。
基本算术任一参数可为浮点,但不得含非标量(线性多项式)项;结果恒为浮点。
一元 "float" 将整数转浮点(最高优先级)。
"trunc" 对浮点取整数部分(向零截断),结果为 plain 整数;参数已是整数则不变。
"bsr" 可用于浮点,返回指数:不大于给定数的最大 2 的幂的指数;浮点符号不影响结果。
"shl"/"shr" 第一参数可为浮点,此时按第二参数指定的 2 的幂乘除。

  1. 符号类

有三类 distinct 符号,由符号在源行中可出现的位置决定识别方式。
指令类符号仅在命令第一个标识符处识别;表达式类仅在提供命令参数值时使用。
前文所述定义均创建表达式类符号。"label"、"restore" 为指令类内建符号示例。
同一命名空间允许不同类同名,例如可定义名为 "shl" 的指令,同时存在表达式类运算符 "shl"。
一行中同一标识符依位置可有不同含义:

    ?restore = 1
    restore restore ; remove the value of the expression-class symbol

第三类为标号指令。行首第一个标识符不是指令时,第一标识符成为标号,第二为标号类指令定义的指令。
将 "=" 视为特殊标识符时,可作为标号指令示例。
汇编器含各类内建符号:大小写不敏感;可重定义;无法删除;"restore" 净尽后仍恢复内建默认。
命名空间规则平等适用于各类。例如最近标号子空间中指令类符号可用点前缀执行。
但经父符号指定命名空间时,父符号恒为表达式类;不能指指令的子命名空间,只能指同名表达式类符号的命名空间。

    xor?.mask? := 10101010b
    a = XOR.MASK    ; symbol in the namespace of built-in case-insensitive "XOR"

    label?.test? := 0
    a = LABEL.TEST  ; undefined unless "label?" is defined

含 "test" 的命名空间属于表达式类符号,非现有指令 "label"。无适合 "LABEL" 的表达式类符号时,
选用大小写敏感该名应有的命名空间;故找不到 "test",因其定义在大小写不敏感 "label" 的另一命名空间。

  1. 生成数据

"db" 指令生成字节数据写入输出。后跟一个或多个以逗号分隔的值。数值为单字节;字符串则逐字节输出。

    db 'Hello',13,10        ; generate 7 bytes

可用 "dup" 重复生成同一值。"dup" 前为重复次数的数值表达式,后为待重复值。
也可重复序列,此时 "dup" 后跟括号内整序列(值以逗号分隔)。

    db 4 dup 90h            ; generate 4 bytes
    db 2 dup ('abc',10)     ; generate 8 bytes

单独 "?" 作 "db" 参数值时保留一字节未初始化:推进下一数据地址,但保留字节本身不生成,除非后跟其他数据。
故输出末尾保留的字节不增加文件大小。此类数据称未初始化,常规数据称已初始化。
"rb" 保留由其参数指定数量的字节。

    db ?                    ; reserve 1 byte
    rb 7                    ; reserve 7 bytes

每个生成数据的内建指令(数据伪指令)都有同名标号指令:除生成数据外,在数据起始处定义标号,关联 size 等于该指令数据单位大小。"db"/"rb" 的 size 为 1。

    some db sizeof some     ; generate a byte with value 1

"dw"、"dd"、"dp"、"dq"、"dt"、"ddq"、"dqq"、"ddqq" 类似 "db" 但单位大小不同。单单位内字节序恒为小端。
字符串作值时零填充到单位倍数。"rw"、"rd"、"rp"、"rq"、"rt"、"rdq"、"rqq"、"rdqq" 保留指定数量单位。单位大小见表 1。
"dw"、"dd"、"dq"、"dt"、"ddq" 接受浮点,转为对应大小的浮点格式。
"emit"(同义词 "dbx")以第一参数指定单位大小生成其余参数定义的数据。大小与下一参数可用 ":" 代替逗号分隔以利阅读。
单位大小有专用数据伪指令时,"emit" 效果与传给该专用指令相同。

    emit 2: 0,1000,2000      ; generate three 16-bit values

"file" 从外部文件读数据写入输出。参数为含路径的字符串;可选 ":" 及文件内偏移,再可选逗号及复制字节数。

    file 'data.bin'                 ; insert entire file
    excerpt file 'data.bin':10h,4   ; insert selected four bytes

表 1 数据伪指令
/------------------------------
| 单位 | 生成 | 保留 |
| (字节) | 数据 | 数据 |
|=========|==========|=========|
| 1 | db | rb |
| | file | |
|---------|----------|---------|
| 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 | |
------------------------------/

  1. 条件汇编

"if" 指令使源文块仅在由逻辑表达式指定的条件下汇编。"else if" 结束上一条件块并开新块,
仅当前面条件均未满足且新条件("else if" 的参数)为真时汇编。"else" 结束上一条件块并开始块,
仅当此前条件均未满足时汇编。须用 "end if" 结束整个构造。可有多个或零个 "else if",至多一个 "else"。
逻辑表达式与基本表达式是 distinct 语法实体,由逻辑值经逻辑算符连接。逻辑算符:一元 "~" 非;
"&" 与;"|" 或。先求 "~","&" 与 "|" 从左到右、彼此无优先级。
逻辑值最简单形式可为基本表达式:值非恒定零则为真。
也可用下列算符比较两基本表达式: "=" "<" ">" "<=" ">=" "<>"。

    count = 2
    if count > 1
            db '0'
            db count-1 dup ',0'
    else if count = 1
            db '0'
    end if

如此比较线性多项式时,逻辑值仅当多项式可比(即仅常数项不同)时有效;否则相等性既非恒真也非恒假,汇编器报错。
"relativeto" 产生逻辑值,仅当比较值之差不含变量项时为真。可检查两线性多项式是否可比——仅当变量项相同时为真。
因逻辑表达式惰性求值,可写单一条件:多项式不可比时不报错,可比时才比较:

    if a relativeto b & a > b
            db a - b
    end if

"eqtype" 比较两基本表达式,类型相同(均为代数、均为字符串或均为浮点;代数含线性多项式与整数)时为真。
"eq" 比较两基本表达式,同类型且相等时为真;可检查是否为某字符串、浮点或线性多项式;可比 "=" 无法比较的值。
"defined" 后跟基本表达式:表达式不含无可用定义的符号时为真。只测试分量可用性,不必可计算。
可检查表达式类符号是否已定义;因可前向引用,符号在源后定义时也可能为真。若要排除此情况用 "definite",检查是否均已先定义。
"defined" 后的基本表达式可为空,条件恒真。"definite" 不适用。
"used" 后跟单个标识符:该符号值曾在源中使用 anywhere 时为真。
"assert" 在参数条件不满足时报错。

    assert a < 65536
  1. 宏指令

"macro" 命令以宏指令形式定义新指令。"macro" 与 "end macro" 之间的源文块成为宏指令文本,
在以此方式定义的指令标识符开头的原命令处汇编。

    macro null
            db 0
    end macro

    null            ; "db 0" is assembled here

仅当定义含参数时宏可有实参。"macro" 与所定义符号标识符后可跟以逗号分隔的简单名称列表作为参数。
使用时可跟至多同样数量的逗号分隔实参,依次赋给连续参数。解释宏内任何行之前,与参数对应的名称 token 替换为所赋值。

    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" 仅可在宏内使用。后跟一个或多个逗号分隔的名称,声明这些名在当前宏上下文中属于与此宏关联的特殊命名空间,
而非当前基命名空间,从而每次调用创建唯一符号。声明额外参数,仅影响同一宏内后续对这些名的使用。同一宏内重复声明同名 local 无额外效果。

    macro measured name,string
            local top
            name db string
            top: name.length = top - name
    end macro

    measured hello, 'Hello!'        ; hello.length = 6

"local" 创建的参数替换为含同名但带上下文信息的文本,使标识属于该宏实例的唯一局部命名空间。此类上下文信息在符号变量一节进一步讨论。
宏局部符号永不被视为点开头标识符之基的最近标号。其后代命名空间与主符号树断开;
若以局部符号为 "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

"esc" 在嵌套定义内时,直到最内层宏定义完成才处理掉。故含 "esc" 的定义可放在另一宏内而无需每层重复 "esc"。
宏定义中标识符后跟 "!" 定义无条件宏指令。此类指令类符号在汇编暂停处仍求值——如在条件为假的条件块内或另一宏定义内。
可定义在否则须直接写 "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.
    ~

两个问号组成的标识符定义特殊指令,仅作最后手段:行内无可识别指令时调用。可拦截否则会报「非法指令」的行。
"mvmacro" 取两参数,均为指令类符号标识。将第二参数指定的宏定义移至第一参数标识的符号;对第二符号效果同 "purge"。
可 effectively 重命名宏或暂时禁用后再恢复。受此操作影响的符号变为变量,不可前向引用。

  1. 标号宏指令

"struc" 命令以宏指令形式定义标号指令。除须用 "end struc" 而非 "end macro" 结束外,定义方式与 "macro" 相同。
当命令第一个标识符不是指令且第二个为标号类指令时求值:

    struc some
            db 1
    end struc

    get some        ; "db 1" is assembled here

标号宏内以点开头的标识符不再指先前普通标号的命名空间,而指标定该指令的标号之命名空间。

    struc POINT
            label . : qword
            .x dd ?
            .y dd ?
    end struc

    my POINT        ; defines my.x and my.y

"." 可指的父符号,除非宏生成适当定义,否则未定义。 furthermore 除非在标号宏中定义为实际标号,此符号不被视为周围命名空间的最近标号。
可用宏定义更易用的语法,例如:

    macro struct? definition&
            esc struc definition
                    label . : .%top - .
                    namespace .
    end macro

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

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

    my POINT 10,20

"restruc" 类似 "purge",但作用于标号指令类。"mvstruc" 同 "mvmacro" 但针对标号指令。
与 "macro" 一样,"struc" 可用单独 "?" 作标识符:每当行首符号不被识别为指令时调用,第一标识符之后全部成为标号宏实参。
下例捕获孤立标号(后无字符)并当作常规标号处理,通过为 "def" 参数设默认值 ":":

    struc ? def::&
            . def
    end struc

    orphan
    regular:
    assert orphan = regular

与 "macro" 类似,此特殊变体不覆盖无条件标号指令,除非自身也是无条件的。
"." 是访问标号符号的简便法,但有时需处理标号实际文本。可为此定义特殊参数,名称写在标号宏名称前的括号内:

    struc (name) SYMBOL
            . db `name,0
    end struc

    test SYMBOL
  1. 符号变量与识别上下文

"equ" 是内建标号指令,定义具符号值的表达式类符号。此类值含任意数量 token 的源文片段(可为空),
在表达式中使用时等同在其标识符处插入值文本,效果类似宏参数求值(但参数恒为单名,符号值可藏在复杂标识符后)。
与用 "=" 定义的标准变量相比可能意外,例如:

    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" 恢复。要覆盖符号值(如 "=" 覆盖常规值)应使用 "reequ" 而非 "equ"。
符号值除保留定义时的确切文本外,还保留其中符号应被解释的上下文。故可成为指向其他符号值的可靠链接,
即使在不同上下文使用(含基命名空间改变或以点开头的符号所指改变):

    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" 后跟该符号变量标识符确定的是链接目标状态。
与符号变量值不同,宏指令体本身不携带上下文(但可含来自替换参数因而带上下文的文本片段)。
若在被定义另一宏时展开被调用宏(仅当被调用宏为无条件时可能),不向实参添加上下文,以利保持无上下文性。
也可强制宏实参不向文本添加上下文:此类实参名前加 "&",使文本在宏求值期间在新上下文中重新解释:

    char = 'A'
    other.char = 'W'

    macro both a, &b
            namespace other
                    db a, b
            end namespace
    end macro

    both char+1, char+1 ; db 'B', 'X'
  1. 重复指令块

"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",值为连续 2 的幂。
"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":

    s = x/2
    while x/s <> s
            s = (s+x/s)/2
            if % = 100
                    break
            end if
    end while

"iterate"(同义词 "irp")遍历逗号分隔值列表重复块。第一参数为参数名,逗号后为值列表。每遍参数取列表中一值。

    iterate value, 1,2,3
            db value
    end iterate

与宏实参一样,含逗号的参数值须用 "<" ">" 包围。也可将 "iterate" 第一参数用 "<" ">" 包围以定义多参数。
值列表则按参数个数分段,每遍处理一段,各参数赋对应值:

    iterate <name,value>, a,1, b,2, c,3
            name = value
    end iterate

参数名也可像宏一样跟 "*" 或 ":" 及默认值。若 "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" 仅为 plain 文本替换。
"iterate"、"repeat" 等定义的参数在关联块文本各处预处理,但若块部分由宏文本定义、部分在他处定义则有限制:参数仅在与起始命令同定义域的块部分可访问。
每次定义参数时,名后可附 "?" 表示大小写不敏感。但预处理行中识别参数时,后随 "?" 无关紧要。预处理替换参数值时唯一认得的修饰符是 "`"。
重复指令与 "if" 同属控制伪指令,控制汇编流。各自定义子指令块,以对应 "end" 关闭;嵌套时须正确嵌套——内块须先关闭。故控制伪指令均为无条件——即使在否则跳过的块内也识别。
"postpone" 是另一控制伪指令,使指令块在后续全部源文处理完毕后才汇编。

    dw final_count
    postpone
            final_count = counter
    end postpone
    counter = 0

上例将 "final_count" 的定义推迟到全源处理完,以便访问 "counter" 的最终值。
跟随 "postpone" 的源文汇编包含任何其他 "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" 可将符号变量值 effectively 转为参数;这本身有用,因符号变量仅在指令(标号与否)实参内表达式中展开,而参数在命令处理开始前整行预处理。
例如可重定义由符号变量链接的常规值:

    x = 1
    var equ x
    irpv symbol, var
            indx %%
            symbol = 2
            break
    end irpv
    assert x = 2

上例组合 "indx" 与 "break" 将迭代限制为符号变量最新值。下一节有更好解法。
传给 "irpv" 的变量值非符号时,参数获得计算后产生相同值的文本。值为正数时参数替换为其十进制表示(类似 "%"),否则替换为持有栈中值的代理符号标识符。
"outscope" 在任何宏处理时可用,修改同行下一命令。若该命令定义参数,参数不在当前宏上下文而在调用该宏的源文上下文中创建。

    macro irpv?! statement&
            display 'IRPV wrapper'
            esc outscope irpv statement
    end macro

这不仅可安全在宏内包装控制伪指令,还可创建为文本块定义参数的定制语言构造。
因 "outscope" 须出现在需要它的特定宏文本中,建议与 "esc" 联用(如上),确保整个定义放入另一宏时处理方式相同。

  1. 匹配参数

"match" 是控制伪指令:仅当第二参数指定的文本与第一参数给定的模式匹配时才汇编其块。文本与模式以逗号分隔,文本含分隔符后至行尾的一切。
模式中除 ","、"="(在模式中有特定含义)外的每个特殊字符都字面匹配——须与文本中相同 token 配对。下例第一块汇编,第二块不汇编。

    match +,+
            assert 1        ; positive match
    end match

    match +,-
            assert 0        ; negative match
    end match

引号字符串亦字面匹配,但模式中的名称 token 不同处理。每个名称作通配符,可匹配任意非空 token 序列。匹配成功则创建同名参数,各参数值等于通配符匹配到的文本。

    match a[b], 100h[3]
            dw a+b          ; dw 100h+3
    end match

模式中参数名可额外附 "?" 表示大小写不敏感。
"=" 使后续 token 字面匹配。可匹配名称 token,也可匹配否则有不同含义的特殊字符如 ","、"=" 或名后的 "?"。

    match =a==a, a=8
            db a            ; db 8
    end match

若 "=" 后跟附 "?" 的名称 token,则字面匹配但大小写不敏感:

    match =a?==a, A=8
            db a            ; db 8
    end match

模式中多个通配符时,连续各者尽量少匹配,最后一个取剩余。通配符相邻且无其间字面元素时,第一个至少匹配一个 token,第二个取余下全部:

    match car cdr, 1+2+3
            db car          ; db 1
            db cdr          ; db +2+3
    end match

上例匹配文本须至少两个 token,因各通配符至少需一个非空 token。下例有额外约束,但一般规则相同,第一通配符尽量少取:

    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        ; positive match
    end match

    match ++,+ +
            assert 0        ; negative match
    end match

    match + +,++
            assert 1        ; positive match
    end match

    match + +,+ +
            assert 1        ; positive match
    end match

当模式含 "=" 后跟空白时,文本中该处必须有空白:

    match += +, ++
            assert 0        ; negative match
    end match

    match += +, + +
            assert 1        ; positive match
    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

甚至可在 "else" 块序列中混用 "if" 与 "match"。整个构造须以最后使用的 "if" 或 "match" 对应的 "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"+"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" 的标识符若无 "rmatch" 转回原始文本,会在调用 "has" 的行有效上下文中解释。

  1. 输出区域

"org" 指令开始新的输出区域。此类区域内容写入目标文件紧接先前数据之后,但新区域内地址基于 "org" 参数指定的值。
下一 "org" 开始或源结束时自动关闭当前区域。

    org 100h
    start:                  ; start = 100h

"$" 是表达式类内建符号,恒等于当前地址。故用 "$" 的值定义常量等价于在同点定义标号:

    org 100h
    start = $               ; start = 100h

"$$" 恒等于当前寻址空间基址,在 "org" 开始的区域内与 "org" 参数基址相同。故 "$" 与 "$$" 之差为相对区域起点的当前位置:

    org 2000h
    db 'Hello!'
    size = $ - $$           ; size = 6

"$@" 求值为当前未初始化数据块的基址。若当前位置前无此类数据则等于 "$",否则等于 "$" 减去当前寻址空间内该数据长度。注意保留数据若后跟已初始化数据则不再计为此类。
"section" 类似 "org",但额外裁剪其前所有保留数据, analogous 于文件末尾未初始化数据不写入输出。"section" 后因此可跟已初始化数据定义,
而不会使先前保留数据被零初始化并写入输出。下例三个保留缓冲区仅第一个实际零化并出现在输出中,因其后跟已初始化数据;第二个因 "section" 被裁剪;第三个在文件末尾被截断:

    data1 dw 1
    buffer1 rb 10h          ; zeroed and present in the output

    org 400h
    data dw 2
    buffer2 rb 20h          ; not in the output

    section 1000h
    data3 dw 3
    buffer3 rb 30h          ; not in the output

"$%" 是内建符号,等于若此刻定义已初始化数据时其在输出文件中的偏移。"$%%" 是当前输出文件内偏移。
两者仅在保留一些数据后使用时不同——"$%" 比 "$%%" 大出若后跟已初始化数据则会写入输出的未初始化数据长度。

    db 'Hello!'
    rb 4
    position = $%%          ; position = 6
    next = $%               ; next = 10

上例注释中的值假定源中无其他生成输出的指令。
"virtual" 创建不写入主输出文件的特殊输出区域。此类区域须在 "virtual" 与 "end 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

未指定字节数时,加载串长度由地址关联的 size 决定。
"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" 可修改输出区域中已生成数据。后跟值(自动转为字节串),可选 ":" 及写入字节数(省略时由地址关联 size 定长),"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" 修改区域的最终数据,可复制到不受此约束的另一区域。 analogous 于用变量的最终值定义常量:

    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

区域标号用于表达式时形成线性多项式变量项;该项 metadata 为区域基址。区域标号本身的 metadata(可用 "sizeof")等于区域内当前数据长度。
"load" 与 "store" 另有变体:当 "at" 或 "from" 后跟 ":" 及偏移值时,可按输出内简单偏移读改已生成数据。

    checksum = 0
    repeat $%
            load a : byte from : %-1
            checksum = checksum + a
    end repeat

"restartout" 放弃迄今生成的全部输出并重新开始。可选参数指定新输出区域基址。无参时以当前地址为新区域基址保留当前地址。
"org"、"section"、"restartout" 不能在 "virtual" 块内使用,只能分隔写入输出文件的区域。

  1. 源与输出控制

"include" 从另一文件读取源文并在当前源中继续处理前处理之。参数应为定义文件路径的字符串(格式可因操作系统而异)。
指令与参数间若有 "!",则无条件读取处理另一文件,即使在跳过的块内(另一文件中的无条件指令可被识别)。

    include 'macro.inc'

可可选添加第二参数(与路径逗号分隔),解释为文件读入源流后、处理第一行前执行的命令。
汇编器在当前处理源所在目录查找文件;若无,则尝试启动汇编的目录,以及环境变量 "INCLUDE" 中分号分隔的路径列表。
"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" 或 "format executable",对产生输出几乎无影响,
除 "format executable" 提示在相关系统上可将文件标为可执行。格式语句后可跟 "as" 及定义输出文件扩展名的字符串。
除非从命令行指定输出文件名,否则由主源文件路径去掉扩展名并附加所定义新扩展名(若有)构造。

    format binary as 'com'

"format" 伪指令类似 "end",用其后标识符在大小写不敏感 "format" 符号之子命名空间找指令。该命名空间仅有的内建指令是 "binary" 与 "executable",但可用宏形式定义更多。
内建符号 "time"(旧同义词 "%t")为汇编开始时刻时间戳的常量值。
"file" 值为含当前处理源文件名的字符串。伴随的 "line" 提供该文件中当前行号。宏内访问时保持调用行的值。
若多层宏相互调用,这些符号各处值相同,对应调用最外层宏的行。
"source" 是另一内建符号,值为含主源文件名的字符串。
"retaincomments" 使汇编器将分号当作常规 token,因而在处理前不剥离行内注释。可在 MATCH 模式等处使用分号。

    retaincomments
    macro ? line&
            match instruction ; comment , line
                    virtual
                            comment
                    end virtual
                    instruction
            else
                    line
            end match
    end macro

    var dd ?  ; bvar db ?

"isolatelines" 阻止汇编器随后合并源文中行末反斜杠后的续行。
"removecomments" 恢复分号默认行为;"combinelines" 恢复照常合并续行。

  1. CALM 指令

"calminstruction" 伪指令以编译的专用命令序列形式定义新指令。与基于简单文本替换的常规宏相对,
CALM(Compiled Assembly-Like Macro)指令能在不将任何文本送标准预处理与汇编循环的情况下执行许多操作。可更精细控制、更好错误处理、更快执行。
CALM 指令定义文本中对符号的一切引用在定义时固定。因此 CALM 局部符号在所有执行实例间共享(连续实例可能看到前一实例留下的局部符号值)。
为复用这些引用,CALM 命令一般操作变量, routinely 用新值重写符号。
"calminstruction" 语句遵循与 "macro" 声明相同规则,含 "!" 定义无条件指令、"*" 标记必填实参、":" 给默认值、"&" 表示最后实参吞掉行内剩余文本。
但因 CALM 在标准预处理与汇编循环外操作,其实参不成为预处理参数,而是局部符号变量,每次调用赋新值。
若所定义指令名前另有圆括号括起的名称,语句定义标号指令,括号内名称将接收标号文本。
CALM 定义中仅识别其专用语言的语句。每行首符号须为无修饰的简单名称,仅当在 CALM 命令命名空间(为定制可经大小写不敏感 "calminstruction" 符号锚定)找到大小写不敏感同名符号时才视为有效指令。
若无此类命名指令,首名后可跟 ":" 成为标号,视为属于专用类的的大小写敏感符号。此类符号仅在用作 CALM 跳转命令参数时识别。
须用 "end calminstruction" 关闭定义并恢复正常汇编模式。它不是常规 "end" 命令,而是 CALM 命名空间中同名指令,只接受 "calminstruction" 为参数。
"assemble" 取单一参数,应为符号变量标识符。该变量文本直接送汇编,无预处理(若文本来自指令实参,该行准备时已预处理)。

    calminstruction please? cmd&
            assemble cmd
    end calminstruction

    please display 'Hi!'

"match" 命令在许多方面与同名标准伪指令相似。第一参数应为遵循 "match" 伪指令相同规则的模式。第二参数必须是符号变量标识符,其文本将与模式匹配。
模式中名 token(除以 "=" 字面化的)视为应写入匹配文本部分的变量名。作文本源的同一变量也可在模式中作写入变量。无匹配时所有变量不受影响。

    calminstruction please? cmd&
            match (cmd), cmd
            assemble cmd
    end calminstruction

    please(display 'Hi!')

匹配是否成功也可用 "match" 后的条件跳转 "jyes" 或 "jno" 测试。"jyes" 仅在匹配成功时跳转。

    calminstruction please? cmd&
            match =do? =not? cmd, cmd
            jyes done
            assemble cmd
        done:
    end calminstruction

    please do not display 'Bye!'

为进一步控制处理流,"jump" 可无条件跳转;"exit" 可在任何时候终止 CALM 处理(无参数)。
用作指令实参的符号隐式为局部,但其他标识符若在定义时可见可成为全局符号的固定引用(CALM 中此类引用均视为使用而非定义)。"match" 等命令可能写入全局变量。

    define comment

    calminstruction please? cmd&
            match cmd //comment, cmd
            assemble cmd
    end calminstruction

    please display 'Hi!' // 3
    db comment                      ; db 3 

要强制将符号视为局部,应使用 "local",后跟一个或多个逗号分隔的名称。

    calminstruction please? cmd&
            local comment
            match cmd //comment, cmd
            assemble cmd
    end calminstruction

为确保符号被视为局部,"local" 语句在定义指令时为其赋已定义但不可用的值,不向编译宏的代码放入任何命令。
CALM 模式中通配符名后紧跟 "?" 不影响符号如何识别(所用符号是否大小写不敏感取决于定义指令时局部作用域中有什么)。
相反,用 "?" 修饰通配符名使其可匹配空文本。
"arrange" 像 "match" 的逆操作,可构建含一个或多个符号变量值的文本。第一参数定义存放构造文本的变量;第二参数为与 "match" 相同方式形成的模式(不必在逗号前用 "=" 才能将逗号包含在参数中)。
除 "=" 及以 "=" 为前缀的 token 外,所有非名 token 字面复制到构造文本,不携带识别上下文。未以 "=" 字面化的名 token 视为变量名,其符号值放入构造文本相应位置。

    calminstruction addr? arg
            local base, index
            match base[index], arg
            local cmd
            arrange cmd, =dd base + index
            assemble cmd
    end calminstruction

    addr 8[5]                       ; dd 8 + 5

用合适模式,"arrange" 可将符号值从一变量复制到另一变量或赋固定值(甚至空值)。
模式中使用的变量若为数值而非符号,只要是无非附加项的非负纯数,即转为十进制 token 存入构造的符号值(CALM 外须用 "repeat 1" 技巧):

    digit = 4 - 1

    calminstruction demo
            local cmd
            arrange cmd, =display digit#0h
            assemble cmd
    end calminstruction

    demo                            ; display 3#0h

这是 "arrange" 将非符号值转为可放入文本的符号的唯一情况,不支持其他类型。
"compute" 可求值表达式并将数值结果赋给变量。第一参数定义存放结果的 target;第二参数可为任意数值表达式,在定义时预编译。
求值时若表达式引用的符号之一为符号值,该文本解析为新子表达式,其计算值再用于主表达式计算。
故 "compute" 不仅可求预定义表达式,还可从符号变量文本(如来自指令实参)解析并计算表达式,或两者组合:

    a = 0

    calminstruction low expr*
            compute a, expr and 0FFh
    end calminstruction

    low 200 + 73                    ; a = 11h

因符号变量作为子表达式求值,此处使用无简单文本替换会造成的副作用。
"check" 类似 "if"。求值其后逻辑表达式定义的条件并相应设置结果标志,可用 "jyes"/"jno" 测试。符号变量值视为数值子表达式(不得含逻辑表达式专用算符)。

    calminstruction u8range? value
            check value >= 0 & value < 256
            jyes ok
            local cmd
            arrange cmd, =err 'value out of range'
            assemble cmd
        ok:
    end calminstruction

    u8range -1

未明确说明设置 "jyes"/"jno" 所检查结果标志的命令,保持该标志不变。
"publish" 可将值赋给由变量所持文本标识的符号。可用 "arrange" 等构造名称,或指令实参传入的名称。
第一参数须为含待定义符号标识符的符号变量;第二参数应为持有待赋值的变量(符号或数值)。第一参数后可跟 ":" 表示符号应为常量,或前跟 ":" 表示值堆叠于先前值之上(可用 "restore" 恢复)。

    calminstruction constdefine? var
            local val
            arrange val,
            match var=  val, var
            publish var:, val
    end calminstruction

    constdefine plus? +

上指令可定义符号常量,标准伪指令无法做到。
"transform" 旨在用符号变量(或常量)的值替换给定文本中的标识符,与 "equ" 准备赋值值时做的相同。
"transform" 参数应为将被如此处理并用变换后文本替换的符号变量。

    calminstruction (var) constequ? val
            transform val
            publish var:, val
    end calminstruction

"transform" 更新结果标志以指示是否做了任何替换。

    calminstruction prepasm? cmd&
        loop:
            transform cmd
            jyes loop               ; warning: may hang on cyclic references
            assemble cmd
    end calminstruction

结果标志仅由部分命令(如 "check"、"match"、"transform")修改,其他命令保持不变。
可选地,"transform" 可有第二参数指定命名空间。第一参数给定文本中的标识符则不论原上下文均在该命名空间中解释。
"stringify" 将变量文本转为字符串并写回同一变量(唯一参数)。类似预处理中 "`" 算符的操作,但产生字符串类型值而非引号字符串。

    calminstruction (var) strcalc? val
            compute val, val        ; compute expression
            arrange val, val        ; convert result to a decimal token
            stringify val           ; convert decimal token to string
            publish var, val
    end calminstruction
                 
    p strcalc 1 shl 1000
    display p

CALM 大多数命令写入时替换变量值;"take" 允许操作值栈。从源符号(第二参数)弹出最顶值给目标符号(第一参数),置于任何已有值之上。
目标参数可为空,此时完全移除值,操作类似 "restore"。此命令更新结果标志指示是否有值可移除。若目标与源相同,可用结果标志检查是否有可用值而不影响它。

    calminstruction reverse? cmd&
            local tmp, stack
        collect:
            match tmp=,cmd, cmd
            take stack, tmp
            jyes collect
        execute:
            assemble cmd
            take cmd, stack
            jyes execute
    end calminstruction

    reverse display '!', display 'i', display 'H' 

被 "take" 作为目标或源访问的符号永不可前向引用,即使否则可以。
在大小写不敏感 "calminstruction" 命名空间定义宏,可向 CALM 指令语言添加定制命令。但须定义为大小写不敏感才被识别。

    macro calminstruction?.asmarranged? variable*, pattern&
            arrange variable, pattern
            assemble variable
    end macro

    calminstruction writeln? text&
            asmarranged text, =display text,10
    end calminstruction

    writeln 'Next!'

此类附加命令甚至可自身定义为 CALM 指令:

    calminstruction calminstruction?.initsym? variable*,value&
            publish variable, value
    end calminstruction

    calminstruction show? text&
            local command
            initsym command, display text
            stringify text
            assemble command
    end calminstruction

    show :)

例中 "initsym" 在定义 "show" 指令时为局部符号变量赋文本。类似 "local"(与 "stringify"、"assemble" 不同),不产生 "show" 调用时执行的代码。
"initsym" 实参保留原上下文,故赋给 "command" 的文本中符号在 "show" 局部命名空间解释。这使 "display" 可访问 "text",尽管 "text" 对 CALM 局部且通常仅在 "show" 定义作用域可见。类似用 "define" 形成符号链接。
"call" 可直接执行另一 CALM 指令。第一参数须提供指令类符号标识;执行时该符号须定义为 CALM(不能此方式调用宏或内建指令)。执行直接进入该指令入口,被调用指令结束后才返回。

    define Msg display 'Hi'

    calminstruction showMsg
            assemble Msg
    end calminstruction

    calminstruction demo
            call showMsg
            arrange Msg, =display '!'
            call showMsg
    end calminstruction

    demo

查找指令符号时,汇编器跳过 CALM 指令的局部命名空间,因预期其中无指令定义。
"call" 的附加参数应为变量(或常量)标识符,其值将作为实参传给被调用指令。这些符号的值直接赋给实参变量,无额外验证——
这可向 CALM 传递否则无法直接传递的值(如数值),因正常调用时实参当作文本并赋为符号值。被调用指令定义允许时可省略实参,此时使用该实参默认值。

    calminstruction hex_nibble digit*, command: display
            compute digit, 0FFh and '0123456789ABCDEF' shr (digit*8)
            arrange command, command digit
            assemble command
    end calminstruction

    calminstruction display_hex_byte value: DATA
            compute value, value
            local   digit
            compute digit, (value shr 4) and 0Fh
            call    hex_nibble, digit
            compute digit, value and 0Fh
            call    hex_nibble, digit
    end calminstruction

    DATA = 0xedfe

    calminstruction demo
            call    display_hex_byte
            compute DATA, DATA shr 8
            call    display_hex_byte
    end calminstruction

    demo

因部分命令(如 "match")不接受非符号(含 token 化文本)以外的值,有 "take" 变体 "taketext",仅当源变量值为此类文本时才执行操作。
被调用指令可用 "taketext" 简单测试实参值是否可用 "match" 检查:

    calminstruction forced_db? value*
            taketext value, value
            jno     proceed
            match   ?, value
            jno     proceed
            arrange value, =rb 1
            assemble value
            exit
         proceed:
            compute value, value and 0FFh
            arrange value, =db value
            assemble value
    end calminstruction

    calminstruction xoredbyte a, b
            compute a, a xor b
            call    forced_db, a
    end calminstruction
  1. CALM 中的汇编命令

CALM 指令另有命令集,使其不仅用于预处理,还可直接生成和处理输出。它们执行基本操作,大多针对单一数据单位,同时可在位进行许多计算,
因除少数例外外,参数为预编译表达式,类似 "compute" 第二参数。
"display" 命令将字节串作为消息呈现在标准输出,与同名常规伪指令相同。取单一参数,表达式给出字符串或单字节数值。

    calminstruction println? text
            display text bappend 13 bappend 10
    end calminstruction

"err" 报错,类似基础语言同名指令。取单一参数指定要呈现的自定义消息。参数应求值为字符串。

    calminstruction check8bit? value*
            check   value and 0FFh = value
            jyes    ok
            stringify value
            err     value bappend ' is out of range'
        ok:
    end calminstruction

"emit" 生成长度由第一参数、值由第二参数指定的数据。两参数均作预编译表达式。第二参数可选;省略则生成指定长度的未初始化数据。
第二参数为字符串时须 fit 指定大小("lengthof" 算符可能有用)。

    calminstruction bigendian64? value*
            emit    8, value bswap 8
    end calminstruction

"load" 与 "store" 可检查或修改已生成输出或 virtual 块中的值。与基础语言对应物相似但语法不同,均取三个逗号分隔参数。
与后者不同,它们不操作与输出区域关联的地址,而操作原始偏移。指向区域第一字节时所述偏移须为零。
"load" 参数依次为:目标变量、加载偏移、加载字节数。第一参数须为符号标识符;后两者为预编译表达式。第二参数可含区域标号后跟 ":" 及字节索引,或仅为纯数值表达式(迄今全部输出的偏移)。
索引/偏移须求值为绝对数值。加载值恒为指定长度的字符串。

    virtual
            __upper::
            repeat 256, c:0
                    if c >= 'a' & c <= 'z'
                            db c-'a'+'A'
                    else
                            db c
                    end if
            end repeat
    end virtual

    calminstruction (out) uppercase char*
            load    char, __upper:+char, 1
            publish out, char
    end calminstruction

"store" 参数依次为:存储偏移、存储字节数、待存储值(数值或字符串)。后两参数类似 "emit" 参数;前两参数类似 "load" 后两参数。偏移可用区域标号加 ":" 作前缀。

    calminstruction encrypt offset, key
            local   byte
            load    byte, offset, 1
            store   offset, 1, byte xor key
    end calminstruction

要在经典 "load"/"store" 所用地址与 CALM 命令期望的原始偏移间转换,加减区域基址即可。若基址未知,可对区域标号用 "1 metadataof" 算符获得。

  1. CALM 中的高级匹配

CALM "match" 命令比常规对应物能力更强。如前所述,用 "?" 修饰通配符使其可选,可匹配空文本:

    calminstruction show_tokens text&
        loop:
            match   token text?, text
            jno     done
            stringify token
            display token bappend 0A0Dh
            jump    loop
        done:
    end calminstruction

因 CALM "match" 第二参数只是标识符而非自由格式文本,后可跟更多参数。可选第三参数可含一对括号字符,所有通配符须匹配括号正确平衡的文本。

    calminstruction range definition
            match   from-to, definition, ()
            emit    1, from
            emit    1, to
        done:
    end calminstruction

    range (10-3)-10

第三参数所选括号不得出现在模式中任何地方。
若第三参数仅含单个特殊字符,则转而选择可用于修饰通配符的符号。最好选模式中否则不出现的字符,但仍可用 "=" 前缀照常匹配。若所选字符跟在通配符名后,须跟通配符修饰符之一(为大小写不敏感 "calminstruction" 命名空间中表达式类内建符号)。
"name" 修饰符使通配符匹配完整符号标识符:

    calminstruction constdefine? var
            local   val
            arrange val,
            match   var:name val, var, :
            publish var:, val
    end calminstruction

    constdefine plus?+

"expression" 修饰符使通配符匹配完整可计算表达式。确保表达式结构正确,但不尝试计算。额外 "defined" 检查可判断是否亦无非定义值,但即使如此也不保证无错求值:溢出问题等仅在实际计算时出现。

    calminstruction bigendian64? text
            match   text/expression, text, /
            jyes    inspect
            err     'malformed expression'
            exit
        inspect:
            check   defined text
            jyes    evaluate
            err     'expression with unknown quantities'
        evaluate:
            emit    8, text bswap 8
    end calminstruction

以 "number" 修饰的通配符匹配数值字面量(整数或浮点)。以 "quoted" 修饰的匹配引号字符串。

    calminstruction inspect text
            match   text-quoted, text, -
            jyes    quoted
            match   text-expression, text, -
            jno     not
            check   text eqtype ''
            jyes    computed
        not:
            display 'not a string' bappend 0A0Dh
            exit
        quoted:
            display 'string token' bappend 0A0Dh
            exit
        computed:
            display 'computed string' bappend 0A0Dh
    end calminstruction

    inspect 'a'
    inspect 10
    inspect string 10

"equ"、"macro"、"struc" 修饰符使通配符仅接受已定义符号的标识符:分别为符号变量、指令、标号指令。"priorequ"、"priormacro"、"priorstruc" 同理,且确保符号已先定义、非前向引用。

    if_true equ if 1
    if_false equ if 0

    calminstruction ifdef? sym*
            match   sym.name, sym, .
            jno     error
            match   sym.equ, sym, .
            jyes    true
            check   defined sym
            jno     false
        true:
            assemble if_true
            exit
        error:
            err     'not a name'
        false:
            assemble if_false
    end calminstruction

以 "area" 修饰的通配符要求区域标号标识符。
专用通配符不能以其他方式修饰:不能设为可选,且不能在同一命令中组合括号平衡与通配符修饰。
因对某些专用匹配器(如符号标识符匹配器)空白有意义,赋给此类通配符的 token 序列可能含尾随空格。若不想要(如需给匹配标识符加后缀),可用带普通通配符的额外 "match" 剥离空格:

    calminstruction ? &expr&
            local sym, value
            match sym|name == expr, expr, |
            jyes assign
            assemble expr
            exit
        assign:
            compute value, expr
            publish sym:, value
            match sym, sym          ; strip spaces
            arrange sym, sym.=expression
            stringify expr
            publish sym:, expr
    end calminstruction

扫描二维码,在手机上阅读!

评论已关闭

© 2026 小工具搬运工. . 京ICP备17053127号