This library provides yet another binary data structure read/write. The difference between (binary pack) and this is the level of abstraction. This library provides higher abstraction layer of the way how to handle binary data.
Defines a macro named name to read binary data by generic method reader and write binary data by writer.
To use defining macros effectively, both forms' reader and _writer_should be the same name.
The first form defines a macro which takes 5 required arguments and optional
keyword arguments. Let's say the name is define-simple
, the
reader is simple-read
and the write is simple-write
,
Then the definition of defined macro would be like this;
Defines name class whose parent classes are parents and slots are slots.
read-proc must be a procedure which takes one argument, a input port and return number of slots values.
write-proc must be a procedure which takes number of slots plus 1 arguments. The first one is an output port and the rest are the value of the slots of the defined class' instance.
The keyword argument parent-metaclass is a parent class of the metaclass of this class.
The slots form must be a list of slot definitions which must be followings;
name
(name)
(name default)
The first and second form define a slot which name is name and its initial value is #f. The third form defines a slot which name is _name_and its initial is default.
Note that current implemenation does not handle parent classes slots. This is only for seamless operations with other CLOS class.
The second form defines a macro which takes 3 required arguments and optional
keyword arguments. Let's say the name is define-composite
, the
reader is simple-read
and the write is simple-write
,
Then the definition of defined macro would be like this;
Defines a composite data class named name whose parent classes are parents and slots are slots.
It is similar form with define-class
however slots must be a list
of one of the followings.
(name type)
(name type default)
(name (type count))
(name (type count) default)
name must be a symbol which represents the slot name.
type can be class name or eqv?
comparable datum. e.g. keyword.
default can be any object.
count must be a non negative exact integer.
The first form is equivalent with the following form;
(name type #f)
.
And the third form is equivalent with the following form;
(name (type count) #f)
.
The first 2 forms defines a datum slot which the datum is read by _reader_passing type and written by writer.
The rest forms defines an array data represented by a vector.
If the type is not defined by neither of the definition forms, then it is users responsibility to define a method which handles the type.
Following is the simple example to show how to use the macros above.
(import (clos user) (binary data))
;; use the same name of reader and writer
(define-simple-datum-define define-simple sample-read sample-write)
(define-composite-data-define define-composite sample-read sample-write)
(define-simple <simple> ()
(a b (c 0))
(lambda (in) (values (get-u8 in) (get-u8 in) (get-u8 in)))
(lambda (out a b c) (put-u8 out a) (put-u8 out b) (put-u8 out c)))
(define-composite <composite> ()
((d :byte 1)
(e (:byte 4) #vu8(1 2 3 4))
(f <simple>)))
;; :byte reader and writer
(define-method sample-read ((o (eql :byte)) in array-size?)
(if array-size?
(get-bytevector-n in array-size?)
(get-u8 in)))
(define-method sample-write ((type (eql :byte)) o out array-size?)
(if array-size?
(put-bytevector out o)
(put-u8 out o)))
How to use the defined data structure.
;; read as a <composite> object
;; "deeeeabc" in ascii
(define bv #vu8(#x64 #x65 #x65 #x65 #x65 #x61 #x62 #x63))
(call-with-port (open-bytevector-input-port bv)
(lambda (in)
(let ((s (sample-read <composite> in)))
(slot-ref s 'd) ;; => #\d
(slot-ref s 'f) ;; => <simple>
)))
;; write <composite> object
(call-with-bytevector-output-port
(lambda (out)
(let* ((s (make <simple> :a 10 :b 20))
(c (make <composite> :f s)))
;; this can be written like this as well (sample-write o out)
(sample-write <composite> c out))))
;; => #vu8(1 1 2 3 4 10 20 0)