空想犬猫記

※当日記では、犬も猫も空想も扱っておりません。(旧・エト記)

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 のマスコット(右上・案)を描いてみた。まだガーベージコレクタがないので,お漏らししているのがポイント(でもちゃんとオムツは履いているよ)。