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:
(bindat-type (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
:
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.