etolisp 進捗 (18) 〜 package の実装
テストフレームワークも少し進化して,こんな感じでテストを書き始めた(少し冗長なのは,本人のスキル不足と etolisp 自体に最低限の関数しか用意されていないから)。
(defmacro nice-caught (&body body) `(progn (nice-catch 'error ,@body) *last-nice-catch-result*)) (defmacro test-noexpt (&body body) `(progn (setf number-of-tests (1+ number-of-tests)) (if (null (nice-caught ,@body)) (progn (format t "passed ... ~A~%" ',@body) (setf number-of-passed (1+ number-of-passed))) (format t "FAILED ... ~A~%" ',@body)))) (defmacro test-assert-1 (&body exp) `(progn (setf number-of-tests (1+ number-of-tests)) (if (and ,@exp) (progn (format t "passed ... ~A~%" ',@exp) (setf number-of-passed (1+ number-of-passed))) (format t "FAILED ... ~A~%" ',@exp)))) (defmacro test-assert (&body exp) `(test-noexpt (test-assert-1 ,@exp))) (setf number-of-tests 0) (setf number-of-passed 0) (test-noexpt (boundp 'x)) (test-noexpt (symbol-name 'x)) (test-assert (string= (format nil "hoge") "hoge")) (test-assert (string= (format nil "hoge~A" 123) "hoge123")) (test-assert (null (format t "hoge~%"))) (test-assert (= (+ 1 2 3) 6)) (test-assert (= (+ 1 2 3 -1 -1 -1) 3)) (test-assert (= (* 1 2 3 -1 -1 -1) -6)) (test-assert (not (= (/ 100 25) 2))) (format t "~A passed in ~A tests~%" number-of-passed number-of-tests))
細かい話だが,test-noexpt が妙ちくりんな名前なのは,たんに assert と文字数を揃えたかったからである。テストの結果は,こんな感じで表示される。
ERRR: IMPLEMENT_ME ERRR: boundp failed. FAILED ... (boundp (quote x)) : (中略) passed ... (null (format t "hoge~%")) passed ... (test-assert-1 (null (format t "hoge~%"))) passed ... (= (+ 1 2 3) 6) passed ... (test-assert-1 (= (+ 1 2 3) 6)) passed ... (= (+ 1 2 3 -1 -1 -1) 3) passed ... (test-assert-1 (= (+ 1 2 3 -1 -1 -1) 3)) passed ... (= (* 1 2 3 -1 -1 -1) -6) passed ... (test-assert-1 (= (* 1 2 3 -1 -1 -1) -6)) passed ... (not (= (/ 100 25) 2)) passed ... (test-assert-1 (not (= (/ 100 25) 2))) 15 passed in 20 tests
実装してない関数や,シンボルの登録さえしてない関数のテストも書けて,結果に換算できるのが面白い。調子に乗って,print 系の関数と,package 絡みの関数群を全て登録してしまった。nice-catch サマサマ。ただ,無条件に C++ の例外を catch してしまうのは強力すぎるので,nice-catch は runtime_error のみにしようかと計画中。オールマイティな catch は,そうだな… super-catch,complete-catch,ultimate-catch,miracle-catch,almighty-catch,omnipotent-catch そんなところだろうか。
(閑話休題)
テスト用の関数群ができたら,今度はそれをモジュール化したいというのが自然な欲求である。Common-Lisp には Package という名前空間を扱う仕組みが用意されている。言語の仕様としては advanced な部類であるが,実装の観点からみれば,けっこう基本的な機能なので,早めに実装してみた。
今まではランタイムが「名前(文字列)→シンボル(整数値)」というマップを保持していたが,それが「名前→名前ID(整数値)」という扱いになって,新たに導入された package という構造体が「名前ID→シンボル」のマップを持つようにした。つまり今度から,名前をキーにしてシンボルを引く際には「名前→名前ID→シンボル」という参照のしかたをする。文字列からシンボルへの変換は parse 時に完了するので,以降の処理は今まで通り。
やってることは名前空間に対応することで,それが「名前→シンボル」のマップ方法の拡張という形で実装されたんだから,多分これで OK なはず。あとは use-package やら delete-package やらを,ごにょごにょ実装すればよい。
ランタイムが package のリストと「current package」のオブジェクトを持っていて,current package は,Lisp の命令でコロコロすげ替えることができるようになる予定。
仕事中に etolisp のマスコット(右上・案)を描いてみた。まだガーベージコレクタがないので,お漏らししているのがポイント(でもちゃんとオムツは履いているよ)。