SBCLで使用可能なexternal-formatの話の続き。
結局 SB-IMPL::*external-formats* を見ればわかるということで解決はしたのだが、SBCLはそれ自体がLispで書かれているので、関数readを使ってソースファイルからLispフォームを読み出し、内容を解析することでexternal-formatの定義を抽出することができるのではないか、と思いついた。
結論から言うと、一応は可能だった。しかし想像していたよりもはるかに面倒くさかった。以下はその苦労の記録である(完全なコードは最後に載せてある)。
基本的な戦略
まずfindとgrepによる下調べで次のようなことが判明した。
- external-formatは3種類のマクロで定義されている
- define-external-format
- define-external-format/variable-width
- define-multibyte-encoding
- その定義が含まれているのは以下のファイル群である
- src/code/fd-stream.lisp
- src/code/external-formats/*.lisp
よってこれらのファイルを read で読んで、該当するマクロフォームからexternal-formatの名前部分を取り出していけば良いはずである。
(defun external-format-definition-p (form) (when (listp form) (case (first form) ((define-external-format define-external-format/variable-width) (second form)) (define-multibyte-encoding (third form)) (otherwise (apply #'append (mapcar #'external-format-definition-p form))))))
障害1.リーダーマクロ
対象となるソースファイルの中には #! で始まる独自のリーダーマクロを使ったコードが含まれており、そのままではreadで読み込めなかった。このマクロの正体が分からず随分苦労したのだが、どうやら既存の処理系でSBCL自体をコンパイルするときに、#!+ および #!- という形式で、それぞれ #+ と #- に類似する機能を提供するためのものらしい(参考)。そういう特殊な役割を持ったマクロであるために、実行時に使うことはできないようだ(実装は src/cold/shebang.lisp の中にある sb-cold::shebang-reader だが、実行時には sb-cold パッケージは存在しない)。
単純に #! を無視するだけのリーダーマクロでは現在の環境で動かないコードまで読まれてエラーになったので、#+ および #- の実装に処理を転送することにして、どうにか動くようにした。
(defun shebang-reader (stream sub-character infix-parameter) (let ((next-char (read-char stream))) (funcall (if (char= next-char #\+) #'sb-impl::sharp-plus #'sb-impl::sharp-minus) stream sub-character infix-parameter)) (values))
障害2.パッケージ参照
read は、読み出したLispフォームに存在しないパッケージへの参照が含まれているとエラーを起こす。
ところがSBCLのソース中には、ソース中の表記(SBCLのコンパイル中に使われる名前?)と実行時の名前が異なるというパッケージが存在することが分かった。例えばSB!IMPL(動作中はSB-IMPL)、SB!THREAD(動作中はSB-THREAD)などである。
ソースを注意深く読み、試行錯誤を繰り返した結果、sb-impl::bootstrap-package-not-found エラーに対し sb-impl::debootstrap-package 再起動を行ってやれば、これらのパッケージをソース中に表記された名前で参照できるということが分かった。
(defmacro with-debootstrap-package (&body body) `(handler-bind ((sb-impl::bootstrap-package-not-found #'sb-impl::debootstrap-package)) ,@body))
例:
(with-debootstrap-package (find-package "SB!IMPL")) ; => #<package "SB-IMPL"> </package>
……
以上の障害(本当はもっといろいろあるが)を乗り越え、ようやく意図通りの動作をするようになった。以下に全コードを載せておく。しかしexportされていないシンボルを使いまくっているので、バージョンが違うと動かない可能性がある(sbcl-1.0.23-x86-darwin で動作確認)。もちろん、SBCLでしか動かない。