common lispのプログラムにemacsでアタッチ

cursesっていうターミナル表示の制御ライブラリ*1を使ってテトリスを作ってみてるのだけど、途中でエラーが起こったときにデバッグ表示が乱れる。特にマルチスレッドだと目も当てられない。そこでemacsでアタッチしてデバッグすると便利。

ちなみに↓がテトリスの動作風景。ソースはもう少し整理してから公開したいと思う。

開発してた時の手順

処理系はsbcl*2。quicklispを使っている。slimeもquicklispでインストールしてある。
環境は Mac (Mavaricks)、sbcl 1.2.11。
~/quicklisp/local-projects 以下にあるプロジェクトは設定なしに quicklisp で読めるので、
quickproject でこの下に作っている。mainという関数を作って export しておき、

$ sbcl --eval '(ql:quickload :tetris :silent t)' --eval '(tetris:main)' --quit

という感じで走らせる。もしくは

(defun make-tetris-command (&optional (name "tetris"))
  (sb-ext:save-lisp-and-die
   name
   :compression t
   :toplevel #'main
   :executable t))

というのを作っておくと

$ sbcl --eval '(ql:quickload :tetris :silent t)' --eval '(tetris:make-tetris-command)'

でカレントディレクトリに tetris という実行ファイルを作れるので

$ ./tetris

で走らせられる。

デバッグ

ターミナルから

$ sbcl --eval '(ql:quickload :tetris :silent t)'

で起動して

* (ql:quickload :swank)
To load "swank":
  Load 1 ASDF system:
    swank
; Loading "swank"
.
(:SWANK)
* (swank:create-server :port 5555 :style :fd-handler :dont-close t)
;; Swank started at port: 5555.

5555

とする*3。ポートがかぶったりしてたら適当に変えてください。

emacs側で M-x slime-connect localhost 5555 とすると繋がる。

あとは emacs 側のreplで

CL-USER> (tetris:main)

と打てば、コンソール側でプログラムが走り出す。何か問題が起きたら emacs 側でデバッガが立ち上がる。M-n や M-p でスタックトレースを行き来したり、ソースファイルを開いてから修正して C-c C−c でコンパイルして実行再開とかできて便利。

しかし、ゲームを作るというのは、特に曲がりなりにも動くようになってからゲームとして成立しだす時というのはなかなか面白いね。

*1:というかそのbindingのcl-charms

*2:移植性は必要になったときに考えるべきで、最初は1つの処理系だけ使い込むのがよいという話を聞いたし、それは正しいと思う

*3:参考サイトだと :style に :spawn を指定しているのだが、手元だとコンソール側からのキー入力に対する反応が変になってしまった。:fd-handler では特に問題なさそう。