etolisp 進捗 (17) 〜 user interface, test framework
Macro も大体動くようになって(まだまだ細かいところで問題は山積みであるが)いよいよ次は構造体や CLOS の実装を試してみようという気になってきた。ただそのためにはシンボル操作周りの関数が不足しているので,いくつか実装する必要がある。
一つ一つ実装していってもいいのだが,それだとモチベーション管理が難しいので,テストケースを先に書いて,fancy なテスト結果を表示するフレームワークを作ろうと画策。ここで役立つのが nice-catch だ。etolisp では C++ の例外が拾えるので,etolisp のランタイムエラーや,実装レベルの例外が catch できてしまう。いかがなものかと思われるけど,これによって,まだ実装のないエラーを引き起こすコードのテストを書いて,実行して,成功/失敗の統計をとるといったことが etolisp 上でできる。stub を作らなくても実装がなくてもテストが書けるしテスト動く。もちろん,厳密な意味では信頼できないんだけど,大抵は上手く機能すると思う。
(defmacro nice-caught (&body body) `(progn (nice-catch 'error ,@body) *last-nice-catch-result*)) (defmacro test-no-exception (&body body) `(if (null (nice-caught (progn ,@body))) (format t "passed ... ~A~%" '(progn ,@body)) (format t "FAILED ... ~A~%" '(progn ,@body)))) (test-no-exception (boundp 'x)) (test-no-exception (symbol-name 'x))
まず手始めに,上のようなコードで試してみた。*last-nice-catch-result* は etolisp で定義されているグローバル変数で,nice-catch で掴まえた例外が入っていて,例外が発生しなければ nil という仕様。取りあえずの実行結果は以下のような感じ。*1
卵か先か鶏が先かという話になりそうだけど,class や package の実装ができてき次第,少しずつ素敵なフレームワークに育てていこうと思う。
> ./etolisp ../test/test-framework.lisp ERRR: IMPLEMENT_ME ERRR: boundp failed. ERRR: cons::eval failed. ERRR: progn failed. ERRR: cons::eval failed. FAILED ... (progn (boundp (quote x))) passed ... (progn (symbol-name (quote x)))
素敵なテスト結果の表示には素敵な UI が必要だ。それだけでなく,将来 etolisp を色んなアプリケーション,デバイスに組み込んで使うということを意識すると,今のうちに UI を抽象化しておくのがよいと思われたので,全ての I/O を UserInterface クラスを介して行う仕様に変更。runtime 初期化時に UI オブジェクトを渡すようにした。もしかしたら要らないかも知れないけど…。
*1:format の ~: に対応すれば,もう少し cons 量を減らせるのだが,現状はやむなくこの形。