tester
自作 Lisp インタプリタ。関数の内部でグローバル変数を setq できないバグがあった。それを直すと再帰関数がおかしな値を返すようになった。変数への値の束縛のバグで、現在の環境で新しく束縛するか、上位の環境をたどって束縛するかの場合分けが必要だった。試行錯誤して今は正しく動作する、はず。
ようやくだが unit test フレームワークの tester を導入(http://opensource.franz.com/test/index.html)。こんな感じでテスト。
(defmacro with-toy ((&key debug) &body body) (let ((env (gensym)) (form (gensym)) (result (gensym))) `(let ((,env (make-env))) (loop for ,form in ',body for ,result = (eval-form ,env ,form) do (when ,debug (format t ";; ~a~%" ,result)) finally (return ,result))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; test ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (with-tests (:name "math") (test 3 (with-toy () (+ 1 2))) (test 2 (with-toy () (- 3 1))) (test 8 (with-toy () (* 2 4))) (test 0 (with-toy () (- 3 (+ 1 2)))) ) (with-tests (:name "simple-func") (test 4 (with-toy () (defun f(x) (+ x x)) (f 2))) (test 6 (with-toy () (setq x 3) (defun f(x) (+ x x)) (f x))) (test 7 (with-toy () (setq x 2) (defun f(y) (setq x (+ y x))) (f 5) x)) (test 55 (with-toy () (defun fib (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))) (fib 10))) (test 120 (with-toy () (defun fact (n) (if (= n 1) 1 (* n (fact (- n 1))))) (fact 5))) (test 8 (with-toy () (defun f (x) (* x 2)) (setq x 1) (defun g (x) (+ x 3)) (f (g x)))) (test 5 (with-toy () (defun h (x y) (- (* 2 x) (* y y))) (h 7 3))) )
せいぜい200行程度のインタプリタだけど、関数もマクロもある。動きを手で追うことはできるけど、組み合わせとして動作するととても複雑で、自分の手から離れて勝手に動いているような不思議な感覚だ。言語処理系ってのは面白いもんだ。10年前にやっておくべきだった。
もう少しマクロ周りが整理されたら、回り道をしたがスタックマシンへのコンパイラ、マシン語へのコンパイラに進もう。
インタプリタでも楽しすぎるほどだが、コンパイラになるともう一段違う楽しさになるに違いない。
- Tiny-C の講義資料は、スタックマシンへのコンパイラ、マシン語へのコンパイラ、の順。多分これに習う。
- SICP はいきなりレジスタマシン、最初無限メモリー、その後 GC 導入?
- gauche はスタックマシンへのコンパイラ。高速化のためにいろいろなテクニックを駆使しているので、読み解くにはまだまだ実力不足。
- sbcl も難しそう。gcl はどうだろう。
おっと、let とクロージャーのことを忘れていた。それから lambda リストのことも。