;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Binary Records ;;> \macro{(define-binary-record-type [ ...] (block: ...))} ;;> ;;> Defines a new record type that supports serializing to and from ;;> binary ports. The generated procedures accept keyword-style ;;> arguments: ;;> ;;> \itemlist[ ;;> \item{\scheme{(make: )}} ;;> \item{\scheme{(pred: )}} ;;> \item{\scheme{(read: )}} ;;> \item{\scheme{(write: )}} ;;> \item{\scheme{(block: ...)}} ;;> ] ;;> ;;> The fields are also similar to \scheme{define-record-type} but ;;> with an additional type: ;;> ;;> \scheme{(field (type args ...) getter setter)} ;;> ;;> Built-in types include: ;;> ;;> \itemlist[ ;;> \item{\scheme{(u8)} - a single byte in [0, 255]} ;;> \item{\scheme{(u16/le)} - a little-endian short integer} ;;> \item{\scheme{(u16/be)} - a big-endian short integer} ;;> \item{\scheme{(fixed-string )} - a fixed-length utf-8 string} ;;> \item{\scheme{(padded-string (pad ))} - a utf-8 string padded to a given length} ;;> \item{\scheme{(octal )} - an integer in octal string format} ;;> \item{\scheme{(decimal )} - an integer in decimal string format} ;;> \item{\scheme{(hexadecimal )} - an integer in hexadecimal string format} ;;> ] ;;> ;;> In addition, the field can be a literal (char, string or ;;> bytevector), for instance as a file magic sequence or fixed ;;> separator. The fields (and any constants) are serialized in the ;;> order they appear in the block. For example, the header of a GIF ;;> file could be defined as: ;;> ;;> \example{ ;;> (define-binary-record-type gif-header ;;> (make: make-gif-header) ;;> (pred: gif-header?) ;;> (read: read-gif-header) ;;> (write: write-gif-header) ;;> (block: ;;> "GIF89a" ;;> (width (u16/le) gif-header-width) ;;> (height (u16/le) gif-header-height) ;;> (gct (u8) gif-header-gct) ;;> (bgcolor (u8) gif-header-gbcolor) ;;> (aspect-ratio (u8) gif-header-aspect-ratio) ;;> )) ;;> } ;;> ;;> For a more complex example see the \scheme{(chibi tar)} ;;> implementation. ;;> ;;> The binary type itself is a macro used to expand to a predicate ;;> and reader/writer procedures, which can be defined with ;;> \scheme{define-binary-type}. For example, ;;> ;;> \example{ ;;> (define-binary-type (u8) ;;> (lambda (x) (and (exact-integer? x) (<= 0 x 255))) ;;> read-u8 ;;> write-u8) ;;> } (define-syntax define-binary-record-type (syntax-rules () ((define-binary-record-type name x ...) (defrec (x ...) name hidden-make hidden-pred hidden-read hidden-write () () ())))) (define-syntax defrec (syntax-rules (make: pred: read: write: block:) ((defrec () n m p r w ((field-tmp field-read field-read-expr field-write field-write-expr field-get) ...) ((field getter . s) ...) (def-setter ...)) (begin (define-record-type n (m field ...) p (field getter . s) ...) (define r (let ((field-read field-read-expr) ...) (lambda (in) (let* ((field-tmp (field-read in)) ...) (m field ...))))) (define w (let ((field-write field-write-expr) ...) (lambda (x out) (field-write (field-get x) out) ...))) def-setter ...) ;; workaround for impls which strip hygiene from top-level defs ;; for some reason, works in chicken but not across libraries ;; ;; (begin ;; (define-values (n m p getter ... setter ...) ;; (let () ;; (define-record-type n (m field ...) p ;; (field getter . s) ...) ;; (def setter val) ... ;; (values (record-rtd n) m p getter ... setter ...))) ;; (define r ;; (let ((field-read field-read-expr) ...) ;; (lambda (in) ;; (let* ((field-tmp (field-read in)) ...) ;; (m field ...))))) ;; (define w ;; (let ((field-write field-write-expr) ...) ;; (lambda (x out) ;; (field-write (field-get x) out) ...)))) ) ((defrec ((make: x) . rest) n m p r w b f s) (defrec rest n x p r w b f s)) ((defrec ((pred: x) . rest) n m p r w b f s) (defrec rest n m x r w b f s)) ((defrec ((read: x) . rest) n m p r w b f s) (defrec rest n m p x w b f s)) ((defrec ((write: x) . rest) n m p r w b f s) (defrec rest n m p r x b f s)) ((defrec ((block: (field (type . args) getter setter) . fields) . rest) n m p r w b f s) (defrec ((block: (field (type . args) getter setter tmp-setter) . fields) . rest) n m p r w b f s)) ((defrec ((block: (field (type . args) getter setter tmp-setter) . fields) . rest) n m p r w (b ...) (f ...) (s ...)) (defrec ((block: . fields) . rest) n m p r w (b ... (field read-tmp (type read: args) write-tmp (type write: args) getter)) (f ... (field getter tmp-setter)) (s ... (define setter (let ((pred? (type pred: args))) (lambda (x val) (if (not (pred? val)) (error "invalid val for" 'field val)) (tmp-setter x val))))))) ((defrec ((block: (field (type . args) getter) . fields) . rest) n m p r w (b ...) (f ...) s) (defrec ((block: . fields) . rest) n m p r w (b ... (field read-tmp (type read: args) write-tmp (type write: args) getter)) (f ... (field getter)) s)) ((defrec ((block: (field . x)) . rest) n m p r w b f s) (syntax-error "invalid field in block" (field . x))) ((defrec ((block: data . fields) . rest) n m p r w (b ...) f s) (defrec ((block: . fields) . rest) n m p r w (b ... (tmp-data read-tmp (read-literal 'data) write-tmp (write-literal 'data) (lambda (x) x))) f s)) ((defrec ((block:) . rest) n m p r w b f s) (defrec rest n m p r w b f s)) ))