40.20.3 Advanced data layout specifications

Bindat type expressions are not limited to the types described earlier. They can also be arbitrary Lisp forms returning Bindat type expressions. For example, the type below describes data which can either contain a 24-bit error code or a vector of bytes:

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

Furthermore, while composite types are normally unpacked to (and packed from) association lists, this can be changed via the use of the following special keyword arguments:

:unpack-val exp

When the list of fields ends with this keyword argument, then the value returned when unpacking is the value of exp instead of the standard alist. exp can refer to all the previous fields by their name.

:pack-val exp

If a field’s type is followed by this keyword argument, then the value packed into this field is returned by exp instead of being extracted from the alist.

:pack-var name

If the list of fields is preceded by this keyword argument, then all the subsequent :pack-val arguments can refer to the overall value to pack into this composite type via the variable named name.

For example, one could describe a 16-bit signed integer as follows:

(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))))

Which would then behave as follows:

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

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

Finally, you can define new Bindat type forms to use in Bindat type expressions with bindat-defmacro:

Macro: bindat-defmacro name args &rest body

Define a new Bindat type expression named name and taking arguments args. Its behavior follows that of defmacro, which the important difference that the new forms can only be used within Bindat type expressions.