バイナリストリームを対話環境でテストする
Java逆アセンブラを作っていたときの話。
バイナリファイルを読み込んで色々するプログラムを書くので、
「バイナリストリームからNバイト読み込んで…」
といった関数を沢山書いたんですが、これのテストがやりにくいです。
ファイルを実際に開く以外に、バイナリストリームを作る方法を知らないので、
ちょっとしたテストをする場合にも、実際にファイルを作らないといけない。
これでは非常に面倒です。
文字ストリームを扱う関数をテストする場合には、
with-input-from-stream, with-output-to-stream
を使えば、対話環境で楽に試せるので、
これのバイナリストリーム版を作ってみました。
Gray Streamを使っています。
SBCLでテスト済み。
(defclass binary-array-input-stream (fundamental-binary-input-stream)
((array :initarg :array :type (array t (*)))
(index :initarg :start :type fixnum)
(end :initarg :end :type fixnum)))
(defun make-binary-array-input-stream (array &optional (start 0) end)
(make-instance 'binary-array-input-stream
:array array :start start :end (or end (length array))))
(defmethod stream-read-byte ((stream binary-array-input-stream))
(with-slots (index end array) stream
(if (>= index end)
:eof
(prog1 (aref array index)
(incf index)))))
(defmacro with-input-from-binary-array((var array)
&body body)
`(let ((,var (make-binary-array-input-stream ,array)))
(multiple-value-prog1
(unwind-protect
(progn ,@body)
(close ,var)))))
配列の型は本当は (array (unsigned-byte 8) (*)) の方がいいんでしょうが、
そうすると、 #(1 2 3) の記法が使えなくなって面倒なので指定を緩くしました。
ここで、次のような関数を作ったとします。
(defun byte-list->number (byte-list &optional little-endian-p)
(reduce #'(lambda (high low) (+ (ash high 8) low))
(if little-endian-p
(reverse byte-list)
byte-list)))
(defun read-byte-list (n stream)
(let (byte-list)
(dotimes (_ n (nreverse byte-list))
(push (read-byte stream) byte-list))))
これらの関数を対話環境でテストしたければ、次のようにします。
(with-input-from-binary-array (s #(1 44))
(let ((byte-list (read-byte-list 2 s)))
(format t "big endian: ~A~%" (byte-list->number byte-list))
(format t "little endian: ~A~%" (byte-list->number byte-list t))))
big endianは300、little endianは11265となり、ちゃんと動きます。