-
Notifications
You must be signed in to change notification settings - Fork 15
Runtime type selection: The EVAL Type Specifier
(read-binary-type '(eval expression) stream &key (byte-order :little-endian) align element-align)
(defbinary a-structure ()
(the-field default-value :type (eval expression)))
The EVAL
Type Specifier makes it possible to defer the decision of what type a slot will be until the moment it is about to be read (or written). This makes it possible for the type to depend on the values of other slots in the structure, or on anything else you deem necessary. If this type is used as the type of a slot in a DEFBINARY
struct, then the expression
will be evaluated in an environment in which all the slots dealt with so far will be bound to variables with the same name.
If it's used outside of a DEFBINARY
definition, then the expression
will be evaluated using EVAL
in the default environment used by EVAL
.
The expression
must evaluate to a Lisp-Binary type designator, such as (simple-array (terminated-string 1) 12)
.
This type will then be used to decide how to read the datum. Lisp-Binary accomplishes this by:
- Generating the code that would have gone into the
READ-BINARY
orWRITE-BINARY
method at runtime instead of compile time. - If in the context of a
DEFBINARY
form, aLET
form is generated with bindings for all the slots whose values are known, and the generated code is put inside thisLET
form. - The resulting code is passed to
EVAL
to perform the actual read or write.
There is special-case logic to optimize EVAL type specifiers whose type-expression is a case
form where the generated type forms don't require runtime knowledge. The following DEFBINARY type does not call EVAL at read time:
(define-enum example-type 1 () :integer :string)
(defbinary example ()
(type :integer :type example-type)
(value nil :type (eval (case type
(:integer '(unsigned-byte 32))
(:string '(counted-string 2))))))
However, if any of the branches of the case
form requires information that is only bound at runtime, or if any other error happens
while trying to expand types that may be returned by the case form, then the optimization will be skipped, and the type specifier will
be processed using the three-step process described above. For example, this would not be optimized:
(define-enum example-type 1 () :integer :string :large-string)
(defbinary example ()
(type :integer :type example-type)
(count-length 0 :type (unsigned-byte 32))
(value nil :type (eval (case type
(:integer '(unsigned-byte 32))
(:string '(counted-string 2))
(:large-string `(counted-string ,count-length))))) ;; COUNT-LENGTH is only bound at read-time; optimization skipped
An example of an error that would prevent optimization:
(define-enum example-type 1 () :integer :string)
(defbinary example ()
(type :integer :type example-type)
(value nil :type (eval (case type
(:integer (loop bloo foo boo boo)))))) ;; The LOOP macro will fail to expand,
;; so we'll just use EVAL. It'll fail again
;; at read or write time.