バイナリストリームを対話環境でテストする
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となり、ちゃんと動きます。