#lang scheme
(require srfi/13)
(define (pack-char buffer start c size signed? big-endian?)
(bytes-set! buffer start (char->integer c)))
(define (pack-integer buffer start n size signed? big-endian?)
(if (= size 1)
(if signed?
(error "Signed byte integers not supported")
(bytes-set! buffer start n))
(integer->integer-bytes n size signed? big-endian? buffer start)))
(define (pack-real buffer start x size signed? big-endian?)
(real->floating-point-bytes x size big-endian? buffer start))
(define (pack-string buffer start s size)
(let* ((bytes (string->bytes/latin-1 s))
(copy-size (min size (bytes-length bytes))))
(bytes-copy! buffer start bytes (+ start copy-size))))
(define (unpack-char buffer start size signed? big-endian?)
(integer->char (bytes-ref buffer start)))
(define (unpack-integer buffer start size signed? big-endian?)
(if (= size 1)
(if signed?
(error "Signed byte integers not supported")
(bytes-ref buffer start))
(integer-bytes->integer buffer signed? big-endian? start (+ start size))))
(define (unpack-real buffer start size signed? big-endian?)
(floating-point-bytes->real buffer big-endian? start (+ start size)))
(define (unpack-string buffer start size)
(bytes->string/latin-1 (subbytes buffer start (+ start size))))
(define-struct packed-format
(specifier
size
signed?
native-alignment
pack-handler
unpack-handler))
(define packed-format-x
(make-packed-format
#\x
1 #f 1
#f
#f))
(define packed-format-c
(make-packed-format
#\c
1 #f 1
pack-char
unpack-char))
(define packed-format-b
(make-packed-format
#\b
1 #t 1
pack-integer
unpack-integer))
(define packed-format-B
(make-packed-format
#\B
1 #f 1
pack-integer
unpack-integer))
(define packed-format-h
(make-packed-format
#\h
2 #t 1
pack-integer
unpack-integer))
(define packed-format-H
(make-packed-format
#\H
2 #f 2
pack-integer
unpack-integer))
(define packed-format-i
(make-packed-format
#\i
4 #t 4
pack-integer
unpack-integer))
(define packed-format-I
(make-packed-format
#\I
4 #f 4
pack-integer
unpack-integer))
(define packed-format-l
(make-packed-format
#\l
4 #t 4
pack-integer
unpack-integer))
(define packed-format-L
(make-packed-format
#\L
4 #f 4
pack-integer
unpack-integer))
(define packed-format-q
(make-packed-format
#\q
8 #t 8
pack-integer
unpack-integer))
(define packed-format-Q
(make-packed-format
#\Q
8 #f 8
pack-integer
unpack-integer))
(define packed-format-f
(make-packed-format
#\f
4 #t 4
pack-real
unpack-real))
(define packed-format-d
(make-packed-format
#\d
8 #t 8
pack-real
unpack-real))
(define packed-format-s
(make-packed-format
#\s
1 #f 1
pack-string
unpack-string))
(define packed-format-specifiers
'(#\x #\c #\b #\B #\h #\H #\i #\I #\l #\L #\q #\Q #\f #\d))
(define packed-format-alist
`((#\x . ,packed-format-x)
(#\c . ,packed-format-c)
(#\b . ,packed-format-b)
(#\B . ,packed-format-B)
(#\h . ,packed-format-h)
(#\H . ,packed-format-H)
(#\i . ,packed-format-i)
(#\I . ,packed-format-I)
(#\l . ,packed-format-l)
(#\L . ,packed-format-L)
(#\q . ,packed-format-q)
(#\Q . ,packed-format-Q)
(#\f . ,packed-format-f)
(#\d . ,packed-format-d)
(#\s . ,packed-format-s)))
(define (char->packed-format char)
(let ((acell (assv char packed-format-alist)))
(if acell (cdr acell) #f)))
(define packed-format-byte-order-regexp #px"^@|=|<|>|!")
(define packed-format-regexp
#px"\\s*(\\d*)(x|c|b|B|h|H|i|I|l|L|q|Q|f|d|s)\\s*")
(define (packed-format-for-each proc packed-format-string)
(let* ((byte-order-match
(regexp-match packed-format-byte-order-regexp packed-format-string))
(byte-order-spec
(if byte-order-match
(string-ref (first byte-order-match) 0)
#\@))
(start
(if byte-order-match 1 0))
(packed-format-specs
(regexp-match* packed-format-regexp packed-format-string start)))
(unless (= (foldl + start (map string-length packed-format-specs))
(string-length packed-format-string))
(error "Packed format string error."))
(for-each
(lambda (packed-format-spec)
(let* ((packed-format-spec-match
(regexp-match packed-format-regexp packed-format-spec))
(count
(if (> (string-length (second packed-format-spec-match)) 0)
(string->number (second packed-format-spec-match))
1))
(specifier
(string-ref (third packed-format-spec-match) 0)))
(proc byte-order-spec packed-format-spec count specifier)))
(map string-trim-both packed-format-specs))))
(define (pad-count offset alignment)
(modulo (- alignment (modulo offset alignment)) alignment))
(define (calculate-size packed-format-string)
(let ((size 0))
(packed-format-for-each
(lambda (byte-order-spec packed-format-spec count specifier)
(let ((packed-format (char->packed-format specifier)))
(when (eqv? byte-order-spec #\@)
(set! size (+ size (pad-count size (packed-format-native-alignment packed-format)))))
(set! size (+ size (* count (packed-format-size packed-format))))))
packed-format-string)
size))
(define (pack-into packed-format-string buffer offset . values)
(packed-format-for-each
(lambda (byte-order-spec packed-format-spec count specifier)
(let* ((packed-format (char->packed-format specifier))
(size (packed-format-size packed-format))
(signed? (packed-format-signed? packed-format))
(native-alignment (packed-format-native-alignment packed-format))
(pack-handler (packed-format-pack-handler packed-format))
(aligned? (if (eqv? byte-order-spec #\@) #t #f))
(big-endian? (if (memv byte-order-spec '(#\@ #\=))
(system-big-endian?)
(if (eqv? byte-order-spec #\<) #f #t))))
(when aligned?
(set! offset (+ offset (pad-count offset native-alignment))))
(cond ((eqv? specifier #\x)
(set! offset (+ offset count)))
((eqv? specifier #\s)
(pack-handler buffer offset (car values) count)
(set! values (cdr values))
(set! offset (+ offset count)))
(else
(for ((i (in-range count)))
(pack-handler buffer offset (car values) size signed? big-endian?)
(set! values (cdr values))
(set! offset (+ offset size)))))))
packed-format-string)
buffer)
(define (pack packed-format-string . values)
(let* ((buffer-size (calculate-size packed-format-string))
(buffer (make-bytes buffer-size)))
(apply pack-into packed-format-string buffer 0 values)))
(define (unpack-from packed-format-string buffer (offset 0))
(let ((buffer-size (bytes-length buffer))
(values '()))
(packed-format-for-each
(lambda (byte-order-spec packed-format-spec count specifier)
(let* ((packed-format (char->packed-format specifier))
(size (packed-format-size packed-format))
(signed? (packed-format-signed? packed-format))
(native-alignment (packed-format-native-alignment packed-format))
(unpack-handler (packed-format-unpack-handler packed-format))
(aligned? (if (eqv? byte-order-spec #\@) #t #f))
(big-endian? (if (memv byte-order-spec '(#\@ #\=))
(system-big-endian?)
(if (eqv? byte-order-spec #\<) #f #t))))
(when aligned?
(set! offset (+ offset (pad-count offset native-alignment))))
(cond ((eqv? specifier #\x)
(set! offset (+ offset count)))
((eqv? specifier #\s)
(set! values (append values (list (unpack-handler buffer offset count))))
(set! offset (+ offset count)))
(else
(for ((i (in-range count)))
(set! values (append values (list (unpack-handler buffer offset size signed? big-endian?))))
(set! offset (+ offset size)))))))
packed-format-string)
values))
(define (unpack packed-format-string buffer)
(unpack-from packed-format-string buffer 0))
(define (write-packed packed-format-string port . values)
(let ((bytes (apply pack packed-format-string values)))
(write-bytes bytes port)))
(define (read-packed packed-format-string port)
(let* ((buffer-size (calculate-size packed-format-string))
(buffer (read-bytes buffer-size port)))
(unpack packed-format-string buffer)))
(define valid-packed-format-string-regexp
#px"^(?:@|=|<|>|!)?(?:\\s*\\d*(?:x|c|b|B|h|H|i|I|l|L|q|Q|f|d|s)\\s*)*$")
(define (packed-format-string? string)
(and (string? string)
(regexp-match-exact? valid-packed-format-string-regexp string)))
(define packed-format-string/c
(flat-named-contract 'packed-format-string/c packed-format-string?))
(provide/contract
(packed-format-string?
(-> any/c boolean?))
(calculate-size
(-> packed-format-string/c (and/c integer? exact? (>=/c 0))))
(pack
(->* (packed-format-string/c) () #:rest (listof any/c) bytes?))
(pack-into
(->* (packed-format-string/c bytes? (and/c integer? exact? (>=/c 0)))
() #:rest (listof any/c)
bytes?))
(unpack
(-> packed-format-string/c bytes? (listof any/c)))
(unpack-from
(->* (packed-format-string/c bytes?) ((and/c integer? exact? (>=/c 0)))
(listof any/c)))
(write-packed
(->* (packed-format-string/c output-port?) () #:rest (listof any/c) any))
(read-packed
(-> packed-format-string/c input-port? (listof any/c))))