バイナリストリームを対話環境でテストする

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となり、ちゃんと動きます。

Leave a Reply