Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
c33aba6
start working on size-object. terminated string not yet
mgi Mar 14, 2017
20a32b4
Better binary type definition. Now, one can define a binary type with
mgi Mar 15, 2017
1c339b5
Renaming (to separate type-size and object-size) + final stroke. Now,
mgi Mar 15, 2017
7fa42e3
remove big endian integers and replace them with little endian intege…
mgi Mar 21, 2017
68f22d6
remove big endian integers and replace them with little endian intege…
mgi Mar 21, 2017
ed23116
Merge branch 'newdatatypes' of github.com:mgi/monkeylib-binary-data i…
mgi Mar 21, 2017
1343979
add little endian floats using ieee-floats library for decoding/encoding
mgi Mar 21, 2017
c008fa8
respect 3 semi-colon convention
mgi Mar 21, 2017
ec6db9b
add vectors
mgi Mar 21, 2017
f09f1cf
add bitfield
mgi Mar 21, 2017
c75a3c4
8bit char fixed length with terminator strings
mgi Mar 21, 2017
0be5ce7
move bitfield where it should be and export it
mgi Mar 22, 2017
c81115c
export 8bit string type
mgi Mar 22, 2017
af8ce52
add a size for typed enumeration
mgi Mar 22, 2017
cdb74f5
fix integer sign
mgi Apr 21, 2017
41b2d81
now type-size has access to the stream and uses it to determine the
mgi Apr 24, 2017
7df1f94
floats are [de/en]coded on top of unsigned integer
mgi Apr 24, 2017
bc71c6c
forgot another stream arg
mgi Apr 24, 2017
4d876ee
type size is not dependent of stream for a lonely integer : stream wi…
mgi Apr 24, 2017
220a15a
make up my mind: type-size does not need access to the stream
mgi Apr 25, 2017
66f602a
use named function for marshalling/unmarshalling
mgi Apr 25, 2017
f5afa5b
optimize vector with read-sequence when relevant
mgi Apr 25, 2017
f1762bf
apply {un}marshaller when it exists only and fix symbol finder
mgi Apr 27, 2017
6731315
(plusp 0) => nil and this was a bug for my 2's complement
mgi Apr 27, 2017
4231357
remove unused function
mgi Nov 28, 2017
8d30086
simply float marshalling/unmarshalling by removing one useless level …
mgi Nov 28, 2017
b08c997
parametrizable endianness
mgi Mar 15, 2018
8914aec
cleaner version for endianness processing
mgi Mar 15, 2018
2f0f965
rework and optimization for vector.
mgi Apr 27, 2018
1a0e8c4
modifying an input array is *bad*
mgi Apr 27, 2018
a15cd3a
big renaming before quicklisp submission
mgi Jun 5, 2018
41b09d4
binary-data is the nickname of com.gigamonkeys.binary-data so use ano…
mgi Jun 5, 2018
4b57832
ensure type for floating point marshalling and export (un)marshaller …
mgi Sep 10, 2018
bb36378
add an optional initform for slots in define-binary-class
mgi Sep 26, 2018
0052b05
long due light documentation
mgi Sep 26, 2018
e10ed70
typo + complete example in README
mgi Sep 26, 2018
acd50a8
some missing float enforcement: use appropriate (un)marshaller
mgi Sep 27, 2018
45ee4d2
do as the other kids and untabify
mgi Oct 3, 2018
1de8ee5
invert if/else and use minusp
mgi Mar 15, 2019
d328c94
default *endianness* based on *features*
mgi Mar 15, 2019
a2d4bf4
setup 1am unit test.
mgi Mar 19, 2019
7949c01
more test
mgi Mar 19, 2019
ccbdafc
cleanup interface: only type-size should be used for object or class
mgi Mar 19, 2019
f9a8bf7
add identity (un)marshaller for unsigned types.
mgi Jul 22, 2019
79fc1a7
no more #'identity (un)marshaller and make its documentation explicit
mgi Jul 24, 2019
fa61a50
use keyword for some handy common-datatypes.
mgi Nov 29, 2019
ad780a5
some missing keyword.
mgi Dec 1, 2019
ff1939e
switch to a more conventional naming.
mgi Dec 1, 2019
7c112d0
[mmap] support for mmap file (sbcl only).
mgi Dec 2, 2019
d0a2c17
typos.
mgi Dec 3, 2019
7a3a820
export fd accessor.
mgi Dec 3, 2019
cb0419c
gray-stream methods should be defined in sb-gray package.
mgi Dec 3, 2019
ccc1eb3
add support for mmap vectors.
mgi Dec 3, 2019
225e1c5
add sb-posix dependence.
mgi Dec 3, 2019
d24334e
read/write for output too.
mgi Dec 3, 2019
65cbf6f
cast for floats.
mgi Dec 9, 2019
c0e9d1a
micro-optimization from https://old.reddit.com/r/Common_Lisp/comments…
mgi Dec 30, 2019
0647162
update test suite (duh!)
mgi Dec 30, 2019
de08589
add a two's complement test.
mgi Dec 30, 2019
ab9adbd
calculate delta from type-size.
mgi Jan 8, 2020
38018ba
typo.
mgi Jan 8, 2020
7eb0e94
a global offset for mmap file is useless. introduce a new
mgi Jan 10, 2020
989ad06
write vector for mmap.
mgi Jan 10, 2020
2a15855
export mmap-stream class.
mgi Jan 10, 2020
a95a7a5
unexport mmap-stream-fd.
mgi Jan 10, 2020
1a731a9
test for negative in vector.
May 11, 2020
fe60ead
performance warning.
mgi May 15, 2020
2dbfb01
exports pack/unpack.
mgi Oct 5, 2020
7ab3e89
test for pack/unpack.
mgi Oct 5, 2020
20282d4
fix toplevel endianness to :little in tests.
mgi Oct 6, 2020
08caced
introduce byte-size for pack/unpack.
mgi Oct 7, 2020
9d059d3
Support for variable length string
mgi Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test:
@sbcl --noinform --eval '(asdf:test-system :binary-io)' \
--eval '(sb-ext:quit)'
64 changes: 64 additions & 0 deletions README.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
* binary-io

This library should be a drop in replacement for monkeylib-binary-data
described in Peter Seibel's excellent book: you should start [[http://gigamonkeys.com/book/practical-parsing-binary-files.html][here]].

It also contains the following optionals enhancements.
** definition/access of octet size of objects
When defining a new binary-type, in addition to =:reader= and
=:writer= definition, you can set a =:size= to calculate the octet
size of this new type. You can then access this size with
=type-size=

Here is an example from =common-datatypes.lisp= (that comes with
=binary-io= ;-)
#+BEGIN_SRC lisp
;;; Unsigned integers
(define-binary-type unsigned-integer (bits)
(:reader (fd)
(assert (equal (stream-element-type fd) '(unsigned-byte 8)))
(let ((byte-indexes (byte-indexes bits *endianness*))
(value 0))
(dolist (i byte-indexes value)
(setf (ldb (byte 8 i) value) (read-byte fd)))))
(:writer (fd value)
(assert (equal (stream-element-type fd) '(unsigned-byte 8)))
(let ((byte-indexes (byte-indexes bits *endianness*)))
(dolist (i byte-indexes)
(write-byte (ldb (byte 8 i) value) fd))))
(:size () (ceiling bits 8)))

(define-binary-type u2 () (unsigned-integer :bits 16))

(type-size 'u2) ;; -> 2
#+END_SRC

=type-size= method also works for binary class.
#+BEGIN_SRC lisp
(define-binary-class test-size ()
((a u2)
(b u2)))

(type-size 'test-size) ;; -> 4
#+END_SRC
** optional initform for slots
You can precise an optional initform for a slot as a third value in
the slot spec of a binary class definition:
#+BEGIN_SRC lisp
(define-binary-class foo-header ()
((tag (8bit-string :length 4 :terminator #\Nul) "FOO")
(counter u2 0)))

(tag (make-instance 'foo-header)) ;; -> "FOO"
#+END_SRC

** a word of warning
binary-io is useful if you have some "complex" data structures that
will be easily mapped by some =define-binary-class=. But if you
have binary data that are mostly arrays of the same type, you'd
better use =read-sequence= directly (with the correct
=element-type= on the stream).

I have made some measurement on SBCL and a =(binary-io:read-value
:vector)= is about 3 times slower than an equivalent
=(read-sequence)=.
185 changes: 129 additions & 56 deletions binary-data.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
;; Copyright (c) 2005, Gigamonkeys Consulting All rights reserved.
;;

(in-package :com.gigamonkeys.binary-data)
(in-package :binary-io)

(defvar *in-progress-objects* nil)

Expand All @@ -12,7 +12,10 @@
(:documentation "Read a value of the given type from the stream."))

(defgeneric write-value (type stream value &key)
(:documentation "Write a value as the given type to the stream."))
(:documentation "Write a value of the given type to the stream."))

(defgeneric type-size (type &key)
(:documentation "Returns the octet size of a type."))

(defgeneric read-object (object stream)
(:method-combination progn :most-specific-last)
Expand All @@ -22,6 +25,12 @@
(:method-combination progn :most-specific-last)
(:documentation "Write out the slots of object to the stream."))

(defgeneric object-size (object)
(:method-combination +)
(:documentation "Returns the octet size of an object."))

;;; Define binary-type's interfaces for binary-class

(defmethod read-value ((type symbol) stream &key)
(let ((object (make-instance type)))
(read-object object stream)
Expand All @@ -31,64 +40,117 @@
(assert (typep value type))
(write-object value stream))

(defmethod type-size (object &key)
"Defaults to low level object size."
(object-size object))

(defmethod type-size ((type symbol) &key)
(object-size (make-instance type)))

;;; Binary types

(defmacro define-binary-type (name (&rest args) &body spec)
(with-gensyms (type stream value)
`(eval-when (:compile-toplevel :load-toplevel :execute)
(defmethod read-value ((,type (eql ',name)) ,stream &key ,@args)
(declare (ignorable ,@args))
,(type-reader-body spec stream))
(defmethod write-value ((,type (eql ',name)) ,stream ,value &key ,@args)
(declare (ignorable ,@args))
,(type-writer-body spec stream value)))))

(defun type-reader-body (spec stream)
(ecase (length spec)
(1 (destructuring-bind (type &rest args) (mklist (first spec))
`(read-value ',type ,stream ,@args)))
(2 (destructuring-bind ((in) &body body)
(cdr (or (assoc :reader spec)
(error "No reader found in ~s" spec)))
`(let ((,in ,stream)) ,@body)))))

(defun type-writer-body (spec stream value)
(ecase (length spec)
(1 (destructuring-bind (type &rest args) (mklist (first spec))
`(write-value ',type ,stream ,value ,@args)))
(2 (destructuring-bind ((out v) &body body)
(cdr (or (assoc :writer spec)
(error "No :writer found in ~s" spec)))
`(let ((,out ,stream) (,v ,value)) ,@body)))))
`(eval-when (:compile-toplevel :load-toplevel :execute)
(defmethod read-value ((,type (eql ',name)) ,stream &key ,@args)
(declare (ignorable ,@args))
,(type-reader-body name spec stream))
(defmethod write-value ((,type (eql ',name)) ,stream ,value &key ,@args)
(declare (ignorable ,@args))
,(type-writer-body name spec stream value))
(defmethod type-size ((,type (eql ',name)) &key ,@args)
(declare (ignorable ,@args))
,(type-size-body name spec)))))

(defun rw-alistp (alist)
"Is alist a ((:reader...) (:writer...) (:size...)) kind of alist."
(and (listp alist)
(every #'consp alist)
(or (assoc :reader alist)
(assoc :writer alist)
(assoc :size alist))))

(defun type-reader-body (name spec stream)
(if (rw-alistp spec)
(let ((reader-spec (assoc :reader spec)))
(if reader-spec
(destructuring-bind ((in) &body body) (cdr reader-spec)
`(let ((,in ,stream)) ,@body))
`(error "No reader defined for type ~s" ',name)))
(destructuring-bind (type &rest args) (mklist (first spec))
`(read-value ',type ,stream ,@args))))

(defun type-writer-body (name spec stream value)
(if (rw-alistp spec)
(let ((writer-spec (assoc :writer spec)))
(if writer-spec
(destructuring-bind ((out v) &body body) (cdr writer-spec)
`(let ((,out ,stream) (,v ,value)) ,@body))
`(error "No writer defined for type ~s" ',name)))
(destructuring-bind (type &rest args) (mklist (first spec))
`(write-value ',type ,stream ,value ,@args))))

(defun type-size-body (name spec)
(if (rw-alistp spec)
(let ((size-spec (assoc :size spec)))
(if size-spec
`(progn ,@(cddr size-spec))
`(error "No size defined for type ~s" ',name)))
(destructuring-bind (type &rest args) (mklist (first spec))
`(type-size ',type ,@args))))

;;; Enumerations

(defmacro define-enumeration (name (type) &rest mapping)
(let ((mapping (normalize-mapping mapping)))
(with-gensyms (in out value)
`(define-binary-type ,name ()
(:reader (,in)
(let ((,value (read-value ',type ,in)))
(case ,value
,@(loop for (symbol number) in mapping collect `(,number ',symbol))
(otherwise (error "No ~a for value: ~a" ',name ,value)))))
(:writer (,out ,value)
(write-value ',type ,out
(case ,value
,@(loop for (symbol number) in mapping collect `(,symbol ,number))
(otherwise (error "~a not a legal ~a" ,value ',name)))))))))

(defun normalize-mapping (mapping)
(loop with number = 0
for entry in mapping collect
(typecase entry
(symbol
(prog1 (list entry number) (incf number)))
(cons
(let ((actual-number (or (second entry) number)))
(prog1 (list (first entry) actual-number)
(setf number (1+ actual-number))))))))
(symbol
(prog1 (list entry number) (incf number)))
(cons
(let ((actual-number (or (second entry) number)))
(prog1 (list (first entry) actual-number)
(setf number (1+ actual-number))))))))

(defmacro define-enumeration (name (type) &rest mapping)
(let ((mapping (normalize-mapping mapping)))
(alexandria:with-gensyms (in out value)
`(define-binary-type ,name ()
(:reader (,in)
(let ((,value (read-value ',type ,in)))
(case ,value
,@(loop for (symbol number) in mapping collect `(,number ',symbol))
(otherwise (error "No ~a for value: ~a" ',name ,value)))))
(:writer (,out ,value)
(write-value ',type ,out
(case ,value
,@(loop for (symbol number) in mapping collect `(,symbol ,number))
(otherwise (error "~a not a legal ~a" ,value ',name)))))
(:size () (type-size ',type))))))

;;; Bitfields
;;;
;;; Here is a bitfield stored in an unsigned 16bit value where the bit
;;; 0 means 'a and bit 1 means 'b:
;;;
;;; (define-bitfield foo (u2)
;;; ((a 0) (b 1)))
(defmacro define-bitfield (name (type) &rest mapping)
(alexandria:with-gensyms (in out value symbol bit encval)
`(define-binary-type ,name ()
(:reader (,in)
(let ((,value (read-value ',type ,in)))
(loop for (,symbol ,bit) in ',@mapping
when (ldb-test (byte 1 ,bit) ,value)
collect ,symbol)))
(:writer (,out ,value)
(write-value ',type ,out
(loop with ,encval = 0
for (,symbol ,bit) in ',@mapping
do (when (member ,symbol ,value)
(setf (ldb (byte 1 ,bit) ,encval) 1))
finally (return ,encval))))
(:size () (type-size ',type)))))

;;; Binary classes

Expand All @@ -98,16 +160,20 @@
(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 (ignorable ,streamvar))
(with-slots ,(new-class-all-slots slots superclasses) ,objectvar
,@(mapcar #'(lambda (x) (slot->write-value x streamvar)) slots))))))
,@(mapcar #'(lambda (x) (slot->write-value x streamvar)) slots)))

(defmethod object-size + ((,objectvar ,name))
(with-slots ,(new-class-all-slots slots superclasses) ,objectvar
(+ ,@(mapcar #'(lambda (x) (slot->object-size x)) slots)))))))

(defmacro define-binary-class (name (&rest superclasses) slots)
(with-gensyms (objectvar streamvar)
Expand Down Expand Up @@ -141,7 +207,7 @@
(defmethod read-value ((,typevar (eql ',name)) ,streamvar &key)
(let* ,(mapcar #'(lambda (x) (slot->binding x streamvar)) slots)
(let ((,objectvar
(make-instance
(make-instance
,@(or (cdr (assoc :dispatch options))
(error "No :dispatch form found in ~s" whole))
,@(mapcan #'slot->keyword-arg slots))))
Expand All @@ -156,8 +222,11 @@
(defun mklist (x) (if (listp x) x (list x)))

(defun slot->defclass-slot (spec)
(let ((name (first spec)))
`(,name :initarg ,(as-keyword name) :accessor ,name)))
(let ((name (first spec))
(initform (third spec)))
(if initform
`(,name :initarg ,(as-keyword name) :initform ,initform :accessor ,name)
`(,name :initarg ,(as-keyword name) :accessor ,name))))

(defun slot->read-value (spec stream)
(destructuring-bind (name (type &rest args)) (normalize-slot-spec spec)
Expand All @@ -167,6 +236,11 @@
(destructuring-bind (name (type &rest args)) (normalize-slot-spec spec)
`(write-value ',type ,stream ,name ,@args)))

(defun slot->object-size (spec)
(destructuring-bind (name (type &rest args)) (normalize-slot-spec spec)
(declare (ignore name))
`(type-size ',type ,@args)))

(defun slot->binding (spec stream)
(destructuring-bind (name (type &rest args)) (normalize-slot-spec spec)
`(,name (read-value ',type ,stream ,@args))))
Expand Down Expand Up @@ -213,4 +287,3 @@ and superclasses have been saved."
(declare (ignore stream))
(let ((*in-progress-objects* (cons object *in-progress-objects*)))
(call-next-method)))

22 changes: 22 additions & 0 deletions binary-io.asd
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
;;
;; Copyright (c) 2005-2011, Peter Seibel. All rights reserved.
;;

(asdf:defsystem binary-io
:description "Library for reading and writing binary data."
:author "Peter Seibel <[email protected]>"
:serial t
:components ((:file "packages")
(:file "binary-data")
(:file "common-datatypes")
#+sbcl (:file "mmap"))
:depends-on (alexandria ieee-floats #+sbcl sb-posix)
:in-order-to ((test-op (test-op :binary-io/test))))

(asdf:defsystem binary-io/test
:description "Test suite for binary-io"
:author "Manuel Giraud <[email protected]>"
:depends-on (:1am :binary-io)
:components ((:file "tests"))
:perform (test-op (o c)
(uiop:symbol-call :1am '#:run)))
13 changes: 0 additions & 13 deletions com.gigamonkeys.binary-data.asd

This file was deleted.

Loading