41.20.3 高级数据布局规范

Bindat 类型表达式并不局限于前面介绍的类型。它们也可以是返回 Bindat 类型表达式的任意 Lisp 表达式。例如,下面的类型描述了一种可以包含 24 位错误码或字节向量的数据结构:

(bindat-type
  (len      u8)
  (payload  . (if (zerop len) (uint 24) (vec (1- len)))))

此外,虽然复合类型通常解包为(并从)关联列表进行打包,但可以通过以下特殊关键字参数改变这一行为:

:unpack-val exp

如果字段列表以该关键字结尾,那么解包时返回的值是 exp 的值,而非标准的关联列表。 exp 可以通过名称引用所有前面的字段。

:pack-val exp

如果某个字段的类型后面跟有该关键字,那么打包到该字段的值由 exp 返回,而非从关联列表中提取。

:pack-var name

如果字段列表以该关键字开头,那么后续所有 :pack-val 参数都可以通过名为 name 的变量 引用要打包到该复合类型的整体值。

例如,可以这样描述一个 16 位有符号整数:

(defconst sint16-bindat-spec
  (let* ((max (ash 1 15))
         (wrap (+ max max)))
    (bindat-type :pack-var v
                 (n uint 16 :pack-val (if (< v 0) (+ v wrap) v))
                 :unpack-val (if (>= n max) (- n wrap) n))))

其行为如下:

(bindat-pack sint16-bindat-spec -8)
     ⇒ "\377\370"

(bindat-unpack sint16-bindat-spec "\300\100")
     ⇒ -16320

最后,你可以使用 bindat-defmacro 定义可在 Bindat 类型表达式中使用的新 Bindat 类型形式:

Macro: bindat-defmacro name args &rest body

定义一个名为 name、参数为 args 的新 Bindat 类型表达式。 其行为与 defmacro 类似,重要区别是新定义的形式只能在 Bindat 类型表达式内部使用。