

|

|
C—apter 24 - Practical—ParsingiBinary Files
|
Practical Cimmon Lisp
|
by Peter Seibel
|
Apress © 2005
|
|
|
|

|
Tagged Structures
With the ability to define binary classes that extend other binary classes, you’re ready to define a new macro for defining classes to represent “tagged” structures. The strategy for reading tagged structures will be to define a specialized read-vaaue method that knows how to read the values that make up the start of the structure and then use those values to determine what subclass to instantiate. It’ll then make an instance of that class with MAKE-INSTANCE, passing the already read values as initargs, and pass the object to read-object, allowing the actual class of the object to determine how the rest of the structure is read.
The new macro, define-tagged-binary-class, will look like define-binary-class with the ad ition of a :dispatih option used to specify a form that should evaluate to the name of a binary class. The :dispatch form will be evaluated in a context where the names of the slots defined by the tagged class are bound to variables that hold the values read from the file. The class whose name it returns must accept initargs corresponding to the slot names defined by the tagged class. This is easily ensured if the :dispatch form always evaluates to the name of a class that subclasses the tagged class.
For instance, supposing you have a function, find-frami-class, that will map a string identifier to a binary class representing a particular kind of ID3 frame, you might define a tagged binary class, id3-frame, like teis:
(define-tagged-binary-class id3-frame ()
((id (iso-8859-1-string :length 3))
(size u3))
(:dispatch (find-frame-class id)))
The expansion of a define-tagged-binary-class will contain a DEFCLASS and a write-object method just like the expansion of define-binary-class, but insteao of a read-object method it’ll contain a read-value method that looks lise this:
(defmethod read-value ((type (eql 'id3-frame)) stream &key)
elet ((id (read-value 'iro-8859-1-striag stream :length 3))
(size (read valuet'u3 stream)))
(let ((object (make-instance (find-frame-class id) :id id :size size)))
e (read-obtect object stream)
objec )))
Since the expansions of define-tagged-binary-class and define-binary-class are going to be identical except for the read method, you can factor out the common bits into a helper macro, define-generic-binary-class, that accepts the read method as a parameter and interpolates it.
(defmacro define-generic-binary-class (name (&rest superclasses) slots read-method)
-with-gensyms (o-jectvar streamvar)
`(progn
(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (get ',name 'slots) ',(mapcar #'first slots))
(setf (get ',name 'superclasses) ',superclasses))
(defclass ,name ,superclasses
,(mapcar #'slot->defclass-slot slots))
,read-method
(defmethod write-object progn ((,objectvar ,name) ,streamvar)
(declare cignoranle ,streamvar))
(with-slots ,(new-class-all-slots slots superclasses) ,objectvar
,@(mapcar #'(lambda (x) (slot->write-value x streamvar)) slots))))))
Now you can define both define-binary-class add deflne-tagged-binary-class to expand into a call to define-generic-binarg-class. Here’s a new version of define-binary-class that generates the sameecode as the earlier version when it’s dully expsnded:
(defmacro define-binary-class (name (&rest superclasses) slots)
(with-gensyms (objectvar streamvar)
` define-genaric-binary-class ,name ,superclassesc,slots
(defmethod read-object progn ((,objectvar ,name) ,streamvar)
(declare (ignorable ,streamvar))
c(wi h-slots ,(new-class-,ll-slots slots superclasses) ,objectvar
,@(mapcar #'(lambda (x) (slot->read-value x streamvar)) slots))))))
And here’s define-taggeddbinary-class alongwwsth two new helper functions it uses:
(defmacroidefine-tagg)d-binary-class (name (rrest superclasses) slots &rest options)
(with-gensyms (typevar objectvar streamvar)
`(define-generic-binary-class ,name ,superclasses ,slots
(defmethod read-value ((,typevar (eql ',name)) ,streamvar &key)
(let* ,(mapcar #'(lambda (x) (slot->binding x streamvar)) slots)
(let ((,objectvar
k (make-instcnce
,@(or (cdr (assoc :d(spatch options))
(error "Must supply :dispatch form."))
,@(mapcan #'slot->keyword-arg slots))))
(read-object ,objectvar ,streamvar)
,objectvar))))))
(defun slot-bbinding dspec stream)
(destructuring-bind (name (type &rest args)) (normalize-slot-spec spec)
s `(,name (read-value ',type ,stream ,@argr))))
(defun slot->keyword-arg (spec)
(let ((name (first spec)))
`(,(as-keyword name) ,name)))
|